From b270265694d8a9822088d6d93ba42cdcbca4ccc8 Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Fri, 28 Feb 2020 18:37:59 +0200 Subject: [PATCH] android: Implement emulator audio playback Former-commit-id: 5d0daee9e7616212aa184e31be360b0110b4c054 --- .../rustboyadvance/EmulatorBindings.java | 7 +++ .../mrmichel/rustboyadvance/PcmStream.java | 12 ----- .../rustdroid_emu/core/AudioThread.java | 47 ++++++++++++++++ .../mrmichel/rustdroid_emu/core/Emulator.java | 10 ++-- .../mrmichel/rustdroid_emu/core/Snapshot.java | 1 - .../rustdroid_emu/core/SnapshotManager.java | 3 -- .../rustdroid_emu/ui/EmulatorActivity.java | 54 +++++++++++++++++-- .../ui/snapshots/SnapshotActivity.java | 9 ---- .../ui/snapshots/SnapshotItemAdapter.java | 3 -- 9 files changed, 110 insertions(+), 36 deletions(-) delete mode 100644 AndroidApp/app/src/main/java/com/mrmichel/rustboyadvance/PcmStream.java create mode 100644 AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/core/AudioThread.java diff --git a/AndroidApp/app/src/main/java/com/mrmichel/rustboyadvance/EmulatorBindings.java b/AndroidApp/app/src/main/java/com/mrmichel/rustboyadvance/EmulatorBindings.java index be93759..870ab57 100644 --- a/AndroidApp/app/src/main/java/com/mrmichel/rustboyadvance/EmulatorBindings.java +++ b/AndroidApp/app/src/main/java/com/mrmichel/rustboyadvance/EmulatorBindings.java @@ -45,6 +45,13 @@ public class EmulatorBindings { */ public static native void runFrame(long ctx, int[] frame_buffer); + /** + * Collect pending audio samples + * @param ctx + * @return sample buffer + */ + public static native short[] collectAudioSamples(long ctx); + /** * @param ctx * @return The loaded ROM title diff --git a/AndroidApp/app/src/main/java/com/mrmichel/rustboyadvance/PcmStream.java b/AndroidApp/app/src/main/java/com/mrmichel/rustboyadvance/PcmStream.java deleted file mode 100644 index 73f96da..0000000 --- a/AndroidApp/app/src/main/java/com/mrmichel/rustboyadvance/PcmStream.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.mrmichel.rustboyadvance; - -import java.io.IOException; -import java.io.InputStream; - -public class AudioStream extends InputStream { - - @Override - public int read(byte[] b) throws IOException { - return super.read(b); - } -} diff --git a/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/core/AudioThread.java b/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/core/AudioThread.java new file mode 100644 index 0000000..89cf780 --- /dev/null +++ b/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/core/AudioThread.java @@ -0,0 +1,47 @@ +package com.mrmichel.rustdroid_emu.core; + +import android.media.AudioTrack; + +public class AudioThread extends Thread { + + AudioTrack audioTrack; + Emulator emulator; + boolean enabled; + boolean stopping; + + public AudioThread(AudioTrack audioTrack, Emulator emulator) { + super(); + this.audioTrack = audioTrack; + this.emulator = emulator; + this.enabled = true; + this.stopping = false; + } + + public void setStopping(boolean stopping) { + this.stopping = stopping; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isStopping() { + return stopping; + } + + public boolean isEnabled() { + return enabled; + } + + @Override + public void run() { + super.run(); + + while (!stopping) { + if (enabled) { + short[] samples = emulator.collectAudioSamples(); + audioTrack.write(samples, 0, samples.length); + } + } + } +} diff --git a/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/core/Emulator.java b/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/core/Emulator.java index e2d26b9..21fc868 100644 --- a/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/core/Emulator.java +++ b/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/core/Emulator.java @@ -1,7 +1,5 @@ package com.mrmichel.rustdroid_emu.core; -import android.graphics.Bitmap; - import com.mrmichel.rustboyadvance.EmulatorBindings; public class Emulator { @@ -32,8 +30,12 @@ public class Emulator { } public synchronized void runFrame() { - EmulatorBindings.setKeyState(this.ctx, this.keypad.getKeyState()); - EmulatorBindings.runFrame(this.ctx, this.frameBuffer); + EmulatorBindings.setKeyState(ctx, keypad.getKeyState()); + EmulatorBindings.runFrame(ctx, frameBuffer); + } + + public synchronized short[] collectAudioSamples() { + return EmulatorBindings.collectAudioSamples(ctx); } public synchronized void setKeyState(int keyState) { diff --git a/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/core/Snapshot.java b/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/core/Snapshot.java index 251d844..3b6de09 100644 --- a/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/core/Snapshot.java +++ b/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/core/Snapshot.java @@ -2,7 +2,6 @@ package com.mrmichel.rustdroid_emu.core; import android.graphics.Bitmap; -import java.io.ByteArrayOutputStream; import java.io.File; public class Snapshot { diff --git a/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/core/SnapshotManager.java b/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/core/SnapshotManager.java index f17e555..a133a9f 100644 --- a/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/core/SnapshotManager.java +++ b/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/core/SnapshotManager.java @@ -9,13 +9,10 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.util.Log; -import java.io.BufferedInputStream; -import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; -import java.io.InputStreamReader; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.sql.Timestamp; diff --git a/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/EmulatorActivity.java b/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/EmulatorActivity.java index 4fc8563..fdfcbb6 100644 --- a/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/EmulatorActivity.java +++ b/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/EmulatorActivity.java @@ -1,10 +1,13 @@ package com.mrmichel.rustdroid_emu.ui; -import android.app.Activity; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Bitmap; +import android.media.AudioFormat; +import android.media.AudioManager; +import android.media.AudioTrack; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import androidx.annotation.NonNull; @@ -13,9 +16,7 @@ import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import android.util.Log; -import android.view.ContextMenu; import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; @@ -25,10 +26,10 @@ import android.widget.Switch; import android.widget.Toast; import com.mrmichel.rustboyadvance.EmulatorBindings; +import com.mrmichel.rustdroid_emu.core.AudioThread; import com.mrmichel.rustdroid_emu.core.Emulator; import com.mrmichel.rustdroid_emu.core.Keypad; import com.mrmichel.rustdroid_emu.R; -import com.mrmichel.rustdroid_emu.core.Snapshot; import com.mrmichel.rustdroid_emu.core.SnapshotManager; import com.mrmichel.rustdroid_emu.ui.snapshots.ChosenSnapshot; import com.mrmichel.rustdroid_emu.ui.snapshots.SnapshotActivity; @@ -39,13 +40,18 @@ import java.io.InputStream; public class EmulatorActivity extends AppCompatActivity implements View.OnClickListener, View.OnTouchListener { private static final String TAG = "EmulatorActivty"; + private static final int LOAD_ROM_REQUESTCODE = 123; private static final int LOAD_SNAPSHOT_REQUESTCODE = 124; + private static int SAMPLE_RATE_HZ = 44100; + private byte[] bios; private Emulator emulator = null; private EmulationRunnable runnable; private Thread emulationThread; + private AudioThread audioThread; + private AudioTrack audioTrack; private byte[] on_resume_saved_state = null; private ImageView screen; private boolean turboMode = false; @@ -169,6 +175,16 @@ public class EmulatorActivity extends AppCompatActivity implements View.OnClickL } emulationThread = null; } + if (audioThread != null) { + audioThread.setStopping(true); + try { + audioThread.join(); + } catch (InterruptedException e) { + Log.e(TAG, "audio thread join interrupted"); + }; + audioThread = null; + } + if (emulator.isOpen()) { emulator.close(); } @@ -193,6 +209,9 @@ public class EmulatorActivity extends AppCompatActivity implements View.OnClickL runnable = new EmulationRunnable(this.emulator, this); emulationThread = new Thread(runnable); emulationThread.start(); + + audioThread = new AudioThread(audioTrack, emulator); + audioThread.start(); } public void loadRomButton(View v) { @@ -216,6 +235,31 @@ public class EmulatorActivity extends AppCompatActivity implements View.OnClickL this.bios = getIntent().getByteArrayExtra("bios"); this.screen = findViewById(R.id.gbaMockImageView); this.emulator = new Emulator(); + + if (Build.VERSION.SDK_INT >= 23) { + AudioTrack.Builder audioTrackBuilder = new AudioTrack.Builder() + .setAudioFormat(new AudioFormat.Builder() + .setEncoding(AudioFormat.ENCODING_PCM_16BIT) + .setSampleRate(SAMPLE_RATE_HZ) + .setChannelMask(AudioFormat.CHANNEL_IN_STEREO | AudioFormat.CHANNEL_OUT_STEREO) + .build() + ) + .setBufferSizeInBytes(4096) + .setTransferMode(AudioTrack.MODE_STREAM); + if (Build.VERSION.SDK_INT >= 26) { + audioTrackBuilder.setPerformanceMode(AudioTrack.PERFORMANCE_MODE_LOW_LATENCY); + } + this.audioTrack = audioTrackBuilder.build(); + } else { + this.audioTrack = new AudioTrack( + AudioManager.STREAM_MUSIC, + SAMPLE_RATE_HZ, + AudioFormat.CHANNEL_IN_STEREO | AudioFormat.CHANNEL_OUT_STEREO, + AudioFormat.ENCODING_PCM_16BIT, + 4096, + AudioTrack.MODE_STREAM); + } + this.audioTrack.play(); } @Override @@ -233,6 +277,7 @@ public class EmulatorActivity extends AppCompatActivity implements View.OnClickL @Override protected void onPause() { super.onPause(); + audioTrack.stop(); if (emulator.isOpen()) { if (runnable != null) { runnable.pauseEmulation(); @@ -261,6 +306,7 @@ public class EmulatorActivity extends AppCompatActivity implements View.OnClickL runnable.resumeEmulation(); } } + audioTrack.play(); } public void onSaveSnapshot(View v) { diff --git a/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/snapshots/SnapshotActivity.java b/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/snapshots/SnapshotActivity.java index b995f28..0549cd5 100644 --- a/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/snapshots/SnapshotActivity.java +++ b/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/snapshots/SnapshotActivity.java @@ -1,22 +1,13 @@ package com.mrmichel.rustdroid_emu.ui.snapshots; -import android.app.Activity; -import android.content.DialogInterface; import android.content.Intent; -import android.net.Uri; import android.os.Bundle; -import com.google.android.material.floatingactionbutton.FloatingActionButton; -import com.google.android.material.snackbar.Snackbar; - -import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.Toolbar; import android.view.View; import android.widget.AdapterView; import android.widget.GridView; -import android.widget.ListView; import android.widget.Toast; import com.mrmichel.rustdroid_emu.R; diff --git a/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/snapshots/SnapshotItemAdapter.java b/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/snapshots/SnapshotItemAdapter.java index df27d87..d0cfa4b 100644 --- a/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/snapshots/SnapshotItemAdapter.java +++ b/AndroidApp/app/src/main/java/com/mrmichel/rustdroid_emu/ui/snapshots/SnapshotItemAdapter.java @@ -5,7 +5,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; -import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; @@ -15,8 +14,6 @@ import androidx.annotation.Nullable; import com.mrmichel.rustdroid_emu.R; import com.mrmichel.rustdroid_emu.core.Snapshot; -import org.w3c.dom.Text; - import java.sql.Timestamp; import java.util.ArrayList;