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);
|
||||
|
||||
/**
|
||||
* Collect pending audio samples
|
||||
* @param ctx
|
||||
* @return sample buffer
|
||||
*/
|
||||
public static native short[] collectAudioSamples(long ctx);
|
||||
|
||||
/**
|
||||
* @param ctx
|
||||
* @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;
|
||||
|
||||
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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Reference in a new issue