android: Implement emulator audio playback
Former-commit-id: 5d0daee9e7616212aa184e31be360b0110b4c054
This commit is contained in:
parent
40360f7839
commit
b270265694
|
@ -45,6 +45,13 @@ public class EmulatorBindings {
|
||||||
*/
|
*/
|
||||||
public static native void runFrame(long ctx, int[] frame_buffer);
|
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
|
* @param ctx
|
||||||
* @return The loaded ROM title
|
* @return The loaded ROM title
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,5 @@
|
||||||
package com.mrmichel.rustdroid_emu.core;
|
package com.mrmichel.rustdroid_emu.core;
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
|
||||||
|
|
||||||
import com.mrmichel.rustboyadvance.EmulatorBindings;
|
import com.mrmichel.rustboyadvance.EmulatorBindings;
|
||||||
|
|
||||||
public class Emulator {
|
public class Emulator {
|
||||||
|
@ -32,8 +30,12 @@ public class Emulator {
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void runFrame() {
|
public synchronized void runFrame() {
|
||||||
EmulatorBindings.setKeyState(this.ctx, this.keypad.getKeyState());
|
EmulatorBindings.setKeyState(ctx, keypad.getKeyState());
|
||||||
EmulatorBindings.runFrame(this.ctx, this.frameBuffer);
|
EmulatorBindings.runFrame(ctx, frameBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized short[] collectAudioSamples() {
|
||||||
|
return EmulatorBindings.collectAudioSamples(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void setKeyState(int keyState) {
|
public synchronized void setKeyState(int keyState) {
|
||||||
|
|
|
@ -2,7 +2,6 @@ package com.mrmichel.rustdroid_emu.core;
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
public class Snapshot {
|
public class Snapshot {
|
||||||
|
|
|
@ -9,13 +9,10 @@ import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
package com.mrmichel.rustdroid_emu.ui;
|
package com.mrmichel.rustdroid_emu.ui;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.media.AudioFormat;
|
||||||
|
import android.media.AudioManager;
|
||||||
|
import android.media.AudioTrack;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
@ -13,9 +16,7 @@ import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.ContextMenu;
|
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
@ -25,10 +26,10 @@ import android.widget.Switch;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.mrmichel.rustboyadvance.EmulatorBindings;
|
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.Emulator;
|
||||||
import com.mrmichel.rustdroid_emu.core.Keypad;
|
import com.mrmichel.rustdroid_emu.core.Keypad;
|
||||||
import com.mrmichel.rustdroid_emu.R;
|
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.core.SnapshotManager;
|
||||||
import com.mrmichel.rustdroid_emu.ui.snapshots.ChosenSnapshot;
|
import com.mrmichel.rustdroid_emu.ui.snapshots.ChosenSnapshot;
|
||||||
import com.mrmichel.rustdroid_emu.ui.snapshots.SnapshotActivity;
|
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 {
|
public class EmulatorActivity extends AppCompatActivity implements View.OnClickListener, View.OnTouchListener {
|
||||||
|
|
||||||
private static final String TAG = "EmulatorActivty";
|
private static final String TAG = "EmulatorActivty";
|
||||||
|
|
||||||
private static final int LOAD_ROM_REQUESTCODE = 123;
|
private static final int LOAD_ROM_REQUESTCODE = 123;
|
||||||
private static final int LOAD_SNAPSHOT_REQUESTCODE = 124;
|
private static final int LOAD_SNAPSHOT_REQUESTCODE = 124;
|
||||||
|
|
||||||
|
private static int SAMPLE_RATE_HZ = 44100;
|
||||||
|
|
||||||
private byte[] bios;
|
private byte[] bios;
|
||||||
private Emulator emulator = null;
|
private Emulator emulator = null;
|
||||||
private EmulationRunnable runnable;
|
private EmulationRunnable runnable;
|
||||||
private Thread emulationThread;
|
private Thread emulationThread;
|
||||||
|
private AudioThread audioThread;
|
||||||
|
private AudioTrack audioTrack;
|
||||||
private byte[] on_resume_saved_state = null;
|
private byte[] on_resume_saved_state = null;
|
||||||
private ImageView screen;
|
private ImageView screen;
|
||||||
private boolean turboMode = false;
|
private boolean turboMode = false;
|
||||||
|
@ -169,6 +175,16 @@ public class EmulatorActivity extends AppCompatActivity implements View.OnClickL
|
||||||
}
|
}
|
||||||
emulationThread = null;
|
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()) {
|
if (emulator.isOpen()) {
|
||||||
emulator.close();
|
emulator.close();
|
||||||
}
|
}
|
||||||
|
@ -193,6 +209,9 @@ public class EmulatorActivity extends AppCompatActivity implements View.OnClickL
|
||||||
runnable = new EmulationRunnable(this.emulator, this);
|
runnable = new EmulationRunnable(this.emulator, this);
|
||||||
emulationThread = new Thread(runnable);
|
emulationThread = new Thread(runnable);
|
||||||
emulationThread.start();
|
emulationThread.start();
|
||||||
|
|
||||||
|
audioThread = new AudioThread(audioTrack, emulator);
|
||||||
|
audioThread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadRomButton(View v) {
|
public void loadRomButton(View v) {
|
||||||
|
@ -216,6 +235,31 @@ public class EmulatorActivity extends AppCompatActivity implements View.OnClickL
|
||||||
this.bios = getIntent().getByteArrayExtra("bios");
|
this.bios = getIntent().getByteArrayExtra("bios");
|
||||||
this.screen = findViewById(R.id.gbaMockImageView);
|
this.screen = findViewById(R.id.gbaMockImageView);
|
||||||
this.emulator = new Emulator();
|
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
|
@Override
|
||||||
|
@ -233,6 +277,7 @@ public class EmulatorActivity extends AppCompatActivity implements View.OnClickL
|
||||||
@Override
|
@Override
|
||||||
protected void onPause() {
|
protected void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
|
audioTrack.stop();
|
||||||
if (emulator.isOpen()) {
|
if (emulator.isOpen()) {
|
||||||
if (runnable != null) {
|
if (runnable != null) {
|
||||||
runnable.pauseEmulation();
|
runnable.pauseEmulation();
|
||||||
|
@ -261,6 +306,7 @@ public class EmulatorActivity extends AppCompatActivity implements View.OnClickL
|
||||||
runnable.resumeEmulation();
|
runnable.resumeEmulation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
audioTrack.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onSaveSnapshot(View v) {
|
public void onSaveSnapshot(View v) {
|
||||||
|
|
|
@ -1,22 +1,13 @@
|
||||||
package com.mrmichel.rustdroid_emu.ui.snapshots;
|
package com.mrmichel.rustdroid_emu.ui.snapshots;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
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.app.AppCompatActivity;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
|
||||||
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.GridView;
|
import android.widget.GridView;
|
||||||
import android.widget.ListView;
|
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.mrmichel.rustdroid_emu.R;
|
import com.mrmichel.rustdroid_emu.R;
|
||||||
|
|
|
@ -5,7 +5,6 @@ import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.ImageButton;
|
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
@ -15,8 +14,6 @@ import androidx.annotation.Nullable;
|
||||||
import com.mrmichel.rustdroid_emu.R;
|
import com.mrmichel.rustdroid_emu.R;
|
||||||
import com.mrmichel.rustdroid_emu.core.Snapshot;
|
import com.mrmichel.rustdroid_emu.core.Snapshot;
|
||||||
|
|
||||||
import org.w3c.dom.Text;
|
|
||||||
|
|
||||||
import java.sql.Timestamp;
|
import java.sql.Timestamp;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
|
Reference in a new issue