Membaca Suara Dengan Perangkat Android


Perangkat Android dalam bentuk ponsel biasanya dilengkapi dengan microphone untuk menerima suara selama penggunaan telepon. Android juga menyediakan API untuk berinteraksi dengan microphone, misalnya MediaRecorder untuk merekam suara ke dalam format terkompresi atau AudioRecord untuk membaca data mentah dari microphone secara langsung.

Pada dasarnya suara dihasilkan oleh sumber suara yang membuat molekul di udara yang bergetar. Molekul di udara tidak bergerak, mereka hanya saling meneruskan getaran. Semakin jauh getaran molekul dari posisi semula, semakin nyaring suara yang terdengar. Untuk menerjemahkan data suara dalam bentuk analog (getaran molekul) menjadi digital (angka), AudioRecord menggunakan metode Pulse-code Modulation (PCM). Setiap perubahan posisi molekul dari jarak semula akan direkam dalam bentuk angka (nilainya ditentukan oleh ENCODING_PCM_16BIT atau ENCODING_PCM_8BIT). Angka ini adalah nilai voltase yang dihasilkan oleh microphone akibat getaran suara. Nilai voltase dari microphone diambil secara periodik tergantung pada sampling rate yang dipakai. Berdasarkan informasi dari dokumentasi Android, nilai 44.100 Hz (mengambil 44.100 sample per detik) adalah nilai sampling rate yang dijamin bekerja dengan baik pada semua perangkat.

Sebagai latihan, saya akan membuat sebuah proyek Android sederhana yang menampilkan sample suara yang dibaca ke layar. Untuk itu, saya membuat sebuah proyek baru di Android Studio. Saya kemudian membuat sebuah activity dengan isi kode program seperti berikut ini:

public class MainActivity extends Activity implements View.OnClickListener {

    private TextView output;
    private Switch aSwitch;
    private int captureSize;
    private AudioRecord audioRecord;
    private boolean isRunning = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        RelativeLayout layout = new RelativeLayout(this);
        aSwitch = new Switch(this);
        aSwitch.setId(View.generateViewId());
        aSwitch.setTextOn("Rekam");
        aSwitch.setOnClickListener(this);
        layout.addView(aSwitch);

        output = new TextView(this);
        RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
        lp.addRule(RelativeLayout.BELOW, aSwitch.getId());
        output.setLayoutParams(lp);
        layout.addView(output);

        setContentView(layout);

        captureSize = AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
        audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, 44100,
            AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, captureSize);
    }

    @Override
    public void onClick(View v) {
        if (aSwitch.isChecked()) {
            audioRecord.startRecording();
            isRunning = true;
            new CaptureThread().start();
        } else {
            isRunning = false;
            audioRecord.stop();
        }
    }

    class CaptureThread extends Thread {

        @Override
        public void run() {
            final short[] buffer = new short[captureSize];
            while(isRunning) {
                audioRecord.read(buffer, 0, captureSize);
                final StringBuilder text = new StringBuilder();
                for (int i=0; i<captureSize; i++) {
                    text.append(buffer[i]);
                    text.append(' ');
                }
                output.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        output.setText(text.toString());
                    }
                }, 100);
            }
        }

    }

}

Sebelum menjalankan aplikasi, saya perlu menambahkan permission berikut ini pada AndroidManifest.xml:

<uses-permission android:name="android.permission.RECORD_AUDIO" />

Bila saya menjalankan aplikasi, saya bisa melihat data suara yang dibaca melalui microphone seperti pada gambar berikut ini:

Tampilan Aplikasi

Tampilan Aplikasi

Pada program di atas, sebelum merekam, saya memanggil startRecording() dari AudioRecord. Setelah selesai merekam, saya memanggil stop() dari AudioRecord. Untuk membaca data suara yang direkam, saya memanggil method read() dari AudioRecord. Method ini membutuhkan sebuah array (bertipe byte atau short tergantung apakah memakai encoding 8-bit atau 16-bit). Ukuran array ini tidak boleh kurang dari nilai yang dikembalikan oleh AudioRecord.getMinBufferSize().

Membaca data dalam bentuk angka lumayan membingungkan bukan? Oleh sebab itu, saya akan mencoba untuk menampilkannya dalam bentuk grafis. Saya akan mulai dengan memindahkan thread ke dalam sebuah class tersendiri yang saya beri nama AnimasiThread dengan isi kode program seperti berikut ini:

public class AnimasiThread extends Thread {

    private SurfaceHolder surfaceHolder;
    private int captureSize;
    private AudioRecord audioRecord;
    private boolean running;
    private int width, height;
    private float midLine, scale;
    private final Paint warnaGaris;

    public AnimasiThread(SurfaceHolder surfaceHolder) {
        this.surfaceHolder = surfaceHolder;
        this.running = false;
        captureSize = AudioRecord.getMinBufferSize(44100, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT);
        audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, 44100, AudioFormat.CHANNEL_IN_MONO,
            AudioFormat.ENCODING_PCM_16BIT, captureSize);
        warnaGaris = new Paint();
        warnaGaris.setColor(Color.GREEN);
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public void setHeight(int height) {
        this.height = height;
        this.midLine = height / 2;
        this.scale =  height / (float) (2 * Short.MAX_VALUE);
    }

    public void startCapture() {
        audioRecord.startRecording();
        running = true;
        start();
    }

    public void stopCapture() {
        running = false;
        audioRecord.stop();
    }

    @Override
    public void run() {
        final short[] buffer = new short[captureSize];
        while(running) {
            audioRecord.read(buffer, 0, captureSize);
            Canvas c = null;
            Float lastX = null, lastY = null;
            try {
                c = surfaceHolder.lockCanvas();
                c.drawColor(Color.BLACK);
                for (int i = 0; i < captureSize; i++) {
                    float trY = midLine - buffer[i] * scale;
                    if ((lastX != null) && (lastY != null)) {
                        c.drawLine(lastX, lastY, i, trY, warnaGaris);
                    } else {
                        c.drawPoint(i, trY, warnaGaris);
                    }
                    lastX = (float) i;
                    lastY = trY;
                }
            } finally {
                if (c != null) {
                    surfaceHolder.unlockCanvasAndPost(c);
                }
            }
        }
    }

}

Thread di atas akan terus menerus membaca data suara dan menampilkannya dalam bentuk grafis dimana garis tengah layar dari height mewakili nilai 0 (tidak ada suara). Nilai yang berada di atas atau di bawah nilai 0 menunjukkan terdapat suara yang menyebabkan membran microphone bergetar.

Selanjutnya, saya membuat sebuah turunan SurfaceView dengan isi seperti berikut ini:

public class GrafikSuara extends SurfaceView implements SurfaceHolder.Callback {

    private AnimasiThread animasiThread;
    private boolean ready = false;
    private int width, height;

    public GrafikSuara(Context context) {
        super(context);
        getHolder().addCallback(this);
    }

    public void mulai() {
        if (ready) {
            animasiThread = new AnimasiThread(getHolder());
            animasiThread.setWidth(width);
            animasiThread.setHeight(height);
            animasiThread.startCapture();
        }
    }

    public void selesai() {
        if (animasiThread != null) {
            animasiThread.stopCapture();
            animasiThread = null;
        }
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        ready = true;
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        this.width = width;
        this.height = height;
        if (animasiThread != null) {
            animasiThread.setWidth(width);
            animasiThread.setHeight(height);
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        if (animasiThread != null) {
            animasiThread.stopCapture();
        }
    }

}

Sebagai langkah terakhir, saya mengubah isi MainActivity menjadi seperti berikut ini:

public class MainActivity extends Activity implements View.OnClickListener {

    private GrafikSuara output;
    private Switch aSwitch;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        RelativeLayout layout = new RelativeLayout(this);
        aSwitch = new Switch(this);
        aSwitch.setId(View.generateViewId());
        aSwitch.setTextOn("Rekam");
        aSwitch.setOnClickListener(this);
        layout.addView(aSwitch);

        output = new GrafikSuara(this);
        RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
        lp.addRule(RelativeLayout.BELOW, aSwitch.getId());
        output.setLayoutParams(lp);
        layout.addView(output);

        setContentView(layout);
    }

    @Override
    protected void onPause() {
        super.onPause();
        output.selesai();
    }

    @Override
    public void onClick(View v) {
        if (aSwitch.isChecked()) {
            output.mulai();
        } else {
            output.selesai();
        }
    }

}

Sekarang, bila saya menjalankan aplikasi, saya akan memperoleh grafis yang terus berubah sesuai suara yang diterima oleh microphone, seperti yang terlihat pada gambar berikut ini:

Tampilan Aplikasi

Tampilan Aplikasi

Perihal Solid Snake
I'm nothing...

Apa komentar Anda?

Please log in using one of these methods to post your comment:

Logo WordPress.com

You are commenting using your WordPress.com account. Logout / Ubah )

Gambar Twitter

You are commenting using your Twitter account. Logout / Ubah )

Foto Facebook

You are commenting using your Facebook account. Logout / Ubah )

Foto Google+

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s

%d blogger menyukai ini: