Merge pull request #109 from hooby3dfx/microphone_support
Microphone support. YARLY.
This commit is contained in:
commit
1d9ec43b22
|
@ -4,6 +4,8 @@
|
|||
#include "maple_devs.h"
|
||||
#include "maple_cfg.h"
|
||||
#include <time.h>
|
||||
#include <android/log.h>
|
||||
#include <jni.h>
|
||||
|
||||
#include "deps/zlib/zlib.h"
|
||||
|
||||
|
@ -225,7 +227,7 @@ struct maple_sega_controller: maple_base
|
|||
return MDRS_DataTransfer;
|
||||
|
||||
default:
|
||||
printf("UNKOWN MAPLE COMMAND %d\n",cmd);
|
||||
//printf("UNKOWN MAPLE COMMAND %d\n",cmd);
|
||||
return MDRE_UnknownFunction;
|
||||
}
|
||||
}
|
||||
|
@ -644,13 +646,191 @@ struct maple_sega_vmu: maple_base
|
|||
|
||||
|
||||
default:
|
||||
printf("Unknown MAPLE COMMAND %d\n",cmd);
|
||||
//printf("Unknown MAPLE COMMAND %d\n",cmd);
|
||||
return MDRE_UnknownCmd;
|
||||
}
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
struct maple_microphone: maple_base
|
||||
{
|
||||
u8 micdata[SIZE_OF_MIC_DATA];
|
||||
|
||||
virtual void OnSetup()
|
||||
{
|
||||
memset(micdata,0,sizeof(micdata));
|
||||
}
|
||||
|
||||
virtual u32 dma(u32 cmd)
|
||||
{
|
||||
//printf("maple_microphone::dma Called 0x%X;Command %d\n",this->maple_port,cmd);
|
||||
//LOGD("maple_microphone::dma Called 0x%X;Command %d\n",this->maple_port,cmd);
|
||||
|
||||
switch (cmd)
|
||||
{
|
||||
case MDC_DeviceRequest:
|
||||
LOGI("maple_microphone::dma MDC_DeviceRequest");
|
||||
//this was copied from the controller case with just the id and name replaced!
|
||||
|
||||
//caps
|
||||
//4
|
||||
w32(MFID_4_Mic);
|
||||
|
||||
//struct data
|
||||
//3*4
|
||||
w32( 0xfe060f00);
|
||||
w32( 0);
|
||||
w32( 0);
|
||||
|
||||
//1 area code
|
||||
w8(0xFF);
|
||||
|
||||
//1 direction
|
||||
w8(0);
|
||||
|
||||
//30
|
||||
wstr(maple_sega_mic_name,30);
|
||||
|
||||
//60
|
||||
wstr(maple_sega_brand,60);
|
||||
|
||||
//2
|
||||
w16(0x01AE);
|
||||
|
||||
//2
|
||||
w16(0x01F4);
|
||||
|
||||
return MDRS_DeviceStatus;
|
||||
|
||||
case MDCF_GetCondition:
|
||||
{
|
||||
LOGI("maple_microphone::dma MDCF_GetCondition");
|
||||
//this was copied from the controller case with just the id replaced!
|
||||
|
||||
//PlainJoystickState pjs;
|
||||
//config->GetInput(&pjs);
|
||||
//caps
|
||||
//4
|
||||
w32(MFID_4_Mic);
|
||||
|
||||
//state data
|
||||
//2 key code
|
||||
//w16(pjs.kcode);
|
||||
|
||||
//triggers
|
||||
//1 R
|
||||
//w8(pjs.trigger[PJTI_R]);
|
||||
//1 L
|
||||
//w8(pjs.trigger[PJTI_L]);
|
||||
|
||||
//joyx
|
||||
//1
|
||||
//w8(pjs.joy[PJAI_X1]);
|
||||
//joyy
|
||||
//1
|
||||
//w8(pjs.joy[PJAI_Y1]);
|
||||
|
||||
//not used
|
||||
//1
|
||||
w8(0x80);
|
||||
//1
|
||||
w8(0x80);
|
||||
}
|
||||
|
||||
return MDRS_DataTransfer;
|
||||
|
||||
case MDC_DeviceReset:
|
||||
//uhhh do nothing?
|
||||
LOGI("maple_microphone::dma MDC_DeviceReset");
|
||||
return MDRS_DeviceReply;
|
||||
|
||||
case MDCF_MICControl:
|
||||
{
|
||||
//LOGD("maple_microphone::dma handling MDCF_MICControl %d\n",cmd);
|
||||
//MONEY
|
||||
u32 function=r32();
|
||||
//LOGD("maple_microphone::dma MDCF_MICControl function (1st word) %#010x\n", function);
|
||||
//LOGD("maple_microphone::dma MDCF_MICControl words: %d\n", dma_count_in);
|
||||
|
||||
switch(function)
|
||||
{
|
||||
case MFID_4_Mic:
|
||||
{
|
||||
//MAGIC HERE
|
||||
//http://dcemulation.org/phpBB/viewtopic.php?f=34&t=69600
|
||||
// <3 <3 BlueCrab <3 <3
|
||||
/*
|
||||
2nd word What it does:
|
||||
0x0000??03 Sets the amplifier gain, ?? can be from 00 to 1F
|
||||
0x0f = default
|
||||
0x00008002 Enables recording
|
||||
0x00000001 Returns sampled data while recording is enabled
|
||||
While not enabled, returns status of the mic.
|
||||
0x00000002 Disables recording
|
||||
*
|
||||
*/
|
||||
u32 secondword=r32();
|
||||
//LOGD("maple_microphone::dma MDCF_MICControl subcommand (2nd word) %#010x\n", subcommand);
|
||||
|
||||
u32 subcommand = secondword & 0xFF; //just get last byte for now, deal with params later
|
||||
|
||||
//LOGD("maple_microphone::dma MDCF_MICControl (3rd word) %#010x\n", r32());
|
||||
//LOGD("maple_microphone::dma MDCF_MICControl (4th word) %#010x\n", r32());
|
||||
switch(subcommand)
|
||||
{
|
||||
case 0x01:
|
||||
{
|
||||
//LOGI("maple_microphone::dma MDCF_MICControl someone wants some data! (2nd word) %#010x\n", secondword);
|
||||
|
||||
w32(MFID_4_Mic);
|
||||
|
||||
//from what i can tell this is up to spec but results in transmit again
|
||||
//w32(secondword);
|
||||
|
||||
//32 bit header
|
||||
w8(0x04);//status (just the bit for recording)
|
||||
w8(0x0f);//gain (default)
|
||||
w8(0);//exp ?
|
||||
|
||||
if(get_mic_data(micdata)){
|
||||
w8(240);//ct (240 samples)
|
||||
wptr(micdata, SIZE_OF_MIC_DATA);
|
||||
}else{
|
||||
w8(0);
|
||||
}
|
||||
|
||||
return MDRS_DataTransfer;
|
||||
}
|
||||
case 0x02:
|
||||
LOGI("maple_microphone::dma MDCF_MICControl toggle recording %#010x\n",secondword);
|
||||
return MDRS_DeviceReply;
|
||||
case 0x03:
|
||||
LOGI("maple_microphone::dma MDCF_MICControl set gain %#010x\n",secondword);
|
||||
return MDRS_DeviceReply;
|
||||
case MDRE_TransminAgain:
|
||||
LOGW("maple_microphone::dma MDCF_MICControl MDRE_TransminAgain");
|
||||
//apparently this doesnt matter
|
||||
//wptr(micdata, SIZE_OF_MIC_DATA);
|
||||
return MDRS_DeviceReply;//MDRS_DataTransfer;
|
||||
default:
|
||||
LOGW("maple_microphone::dma UNHANDLED secondword %#010x\n",secondword);
|
||||
break;
|
||||
}
|
||||
}
|
||||
default:
|
||||
LOGW("maple_microphone::dma UNHANDLED function %#010x\n",function);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
LOGW("maple_microphone::dma UNHANDLED MAPLE COMMAND %d\n",cmd);
|
||||
return MDRE_UnknownFunction;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
maple_device* maple_Create(MapleDeviceType type)
|
||||
{
|
||||
maple_device* rv=0;
|
||||
|
@ -659,6 +839,9 @@ maple_device* maple_Create(MapleDeviceType type)
|
|||
case MDT_SegaController:
|
||||
rv=new maple_sega_controller();
|
||||
break;
|
||||
case MDT_Microphone:
|
||||
rv=new maple_microphone();
|
||||
break;
|
||||
#ifdef HAS_VMU
|
||||
case MDT_SegaVMU:
|
||||
rv = new maple_sega_vmu();
|
||||
|
@ -670,4 +853,4 @@ maple_device* maple_Create(MapleDeviceType type)
|
|||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ enum MapleDeviceType
|
|||
{
|
||||
MDT_SegaController,
|
||||
MDT_SegaVMU,
|
||||
MDT_Microphone,
|
||||
|
||||
MDT_Count
|
||||
};
|
||||
|
@ -27,4 +28,6 @@ struct maple_device
|
|||
virtual u32 Dma(u32 Command,u32* buffer_in,u32 buffer_in_len,u32* buffer_out,u32& buffer_out_len)=0;
|
||||
};
|
||||
|
||||
maple_device* maple_Create(MapleDeviceType type);
|
||||
maple_device* maple_Create(MapleDeviceType type);
|
||||
#define SIZE_OF_MIC_DATA 480 //ALSO DEFINED IN SipEmulator.java
|
||||
int get_mic_data(u8* buffer); //implemented in Android.cpp
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
1/13/2014 hooby3dfx initial plan
|
||||
1/16/2014 hooby3dfx updated with progress, more details on how it works
|
||||
|
||||
|
||||
general info links from bluecrab: http://dreamcast-talk.com/forum/viewtopic.php?t=2921&f=5
|
||||
kallistios driver (could use for test app): http://cadcdev.sourceforge.net/docs/kos-2.0.0/sip_8h.html
|
||||
maplebus: https://web.archive.org/web/20101117090620/http://mc.pp.se/dc/maplebus.html
|
||||
|
||||
plan:
|
||||
-figure out the mic commands that seaman is looking for and implement them [DONE (a couple commands not supported)]
|
||||
-create mic test app (consult bluecrab) [Otoire tunes should be great and the other real games that support mic - should compile list]
|
||||
-figure out how the game/dc requests the audio (or how the mic just streams)... [Done - at least in seaman DC tells mic to start recording then polls the mic pretty frequently for data]
|
||||
-for poc just create some kind of global way to pass the audio data from android mic interface directly to the maple_base instance [Done]
|
||||
-test [Created recording in Otoire, imported VMS into Demul and got the sound! Seaman needs more testing]
|
||||
-cleanup [Partially done, lots of logging left]
|
||||
-test more
|
||||
-integrate into settings ui
|
||||
|
||||
|
||||
16-bit PCM @ 11025 hz
|
||||
== 176.4 kbit/s
|
||||
== 22050 bytes/s
|
||||
|
||||
maximum size of a Maple Bus packet is 256 words (1024 bytes)
|
||||
|
||||
|
||||
================
|
||||
Games that can use mic:
|
||||
-Seaman
|
||||
-Alien Front Online
|
||||
-Planet Ring
|
||||
-Mr Driller
|
||||
-Otoire
|
||||
-Propeller Arena
|
||||
-Visual Park
|
||||
-Kiteretsu Boys Gangagan
|
||||
|
|
@ -11,11 +11,13 @@
|
|||
<uses-permission android:name="android.permission.READ_LOGS" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
|
||||
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
|
||||
|
||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
|
||||
|
||||
<uses-feature android:name="android.hardware.microphone" android:required="false"/>
|
||||
|
||||
<application
|
||||
android:hardwareAccelerated="true"
|
||||
android:largeHeap="true"
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include "cfg/cfg.h"
|
||||
#include "rend/TexCache.h"
|
||||
#include "hw/maple/maple_devs.h"
|
||||
#include "hw/maple/maple_if.h"
|
||||
|
||||
#include "util.h"
|
||||
|
||||
|
@ -35,8 +36,11 @@ extern "C"
|
|||
//JNIEXPORT jint JNICALL Java_com_reicast_emulator_JNIdc_play(JNIEnv *env,jobject obj,jshortArray result,jint size);
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_reicast_emulator_JNIdc_initControllers(JNIEnv *env, jobject obj, jbooleanArray controllers) __attribute__((visibility("default")));
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_reicast_emulator_JNIdc_setupMic(JNIEnv *env,jobject obj,jobject sip) __attribute__((visibility("default")));
|
||||
};
|
||||
|
||||
|
||||
void egl_stealcntx();
|
||||
void SetApplicationPath(wchar *path);
|
||||
int dc_init(int argc,wchar* argv[]);
|
||||
|
@ -191,6 +195,9 @@ jshortArray jsamples;
|
|||
jmethodID writemid;
|
||||
jobject track;
|
||||
|
||||
jobject sipemu;
|
||||
jmethodID getmicdata;
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_reicast_emulator_JNIdc_run(JNIEnv *env,jobject obj,jobject trk)
|
||||
{
|
||||
install_prof_handler(0);
|
||||
|
@ -204,6 +211,14 @@ JNIEXPORT void JNICALL Java_com_reicast_emulator_JNIdc_run(JNIEnv *env,jobject o
|
|||
dc_run();
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_reicast_emulator_JNIdc_setupMic(JNIEnv *env,jobject obj,jobject sip)
|
||||
{
|
||||
sipemu = env->NewGlobalRef(sip);
|
||||
getmicdata = env->GetMethodID(env->GetObjectClass(sipemu),"getData","()[B");
|
||||
delete MapleDevices[0][1];
|
||||
mcfg_Create(MDT_Microphone,0,1);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_com_reicast_emulator_JNIdc_stop(JNIEnv *env,jobject obj)
|
||||
{
|
||||
dc_term();
|
||||
|
@ -330,3 +345,15 @@ bool os_IsAudioBuffered()
|
|||
{
|
||||
return jenv->CallIntMethod(track,writemid,jsamples,-1)==0;
|
||||
}
|
||||
|
||||
int get_mic_data(u8* buffer)
|
||||
{
|
||||
jbyteArray jdata = (jbyteArray)jenv->CallObjectMethod(sipemu,getmicdata);
|
||||
if(jdata==NULL){
|
||||
//LOGW("get_mic_data NULL");
|
||||
return 0;
|
||||
}
|
||||
jenv->GetByteArrayRegion(jdata, 0, SIZE_OF_MIC_DATA, (jbyte*)buffer);
|
||||
jenv->DeleteLocalRef(jdata);
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -92,6 +92,32 @@
|
|||
android:ems="8"
|
||||
android:text="@string/launch_editor" />
|
||||
</LinearLayout>
|
||||
</TableRow>
|
||||
|
||||
<TableRow
|
||||
android:layout_marginTop="25dp"
|
||||
android:gravity="center_vertical" >
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="0.5"
|
||||
android:ems="10"
|
||||
android:gravity="center_vertical|left"
|
||||
android:text="@string/mic_in_port_2" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="right"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<de.ankri.views.Switch
|
||||
android:id="@+id/micInPort2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
</LinearLayout>
|
||||
</TableRow>
|
||||
</TableLayout>
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
<string name="modified_layout">Enable Custom Key Layout</string>
|
||||
<string name="controller_compat">Enable Compatibility Mode</string>
|
||||
<string name="dpad_joystick">Joystick Uses DPAD Layout</string>
|
||||
<string name="mic_in_port_2">Microphone plugged into port 2</string>
|
||||
|
||||
<string name="customize_physical_controls">Customize Physical Controls</string>
|
||||
<string name="map_keycode_title">Modify Controller</string>
|
||||
|
|
|
@ -325,6 +325,14 @@ public class GL2JNIActivity extends Activity {
|
|||
|
||||
Toast.makeText(getApplicationContext(),
|
||||
"Press the back button for a menu", Toast.LENGTH_SHORT).show();
|
||||
|
||||
//setup mic
|
||||
boolean micPluggedIn = prefs.getBoolean("mic_plugged_in", false);
|
||||
if(micPluggedIn){
|
||||
SipEmulator sip = new SipEmulator();
|
||||
sip.startRecording();
|
||||
JNIdc.setupMic(sip);
|
||||
}
|
||||
}
|
||||
|
||||
private void runCompatibilityMode() {
|
||||
|
|
|
@ -39,6 +39,7 @@ public class InputFragment extends Fragment {
|
|||
private AlertDialog alertDialogSelectController;
|
||||
private SharedPreferences sharedPreferences;
|
||||
private Switch switchTouchVibrationEnabled;
|
||||
private Switch micPluggedIntoFirstController;
|
||||
|
||||
public MOGAInput moga = new MOGAInput();
|
||||
|
||||
|
@ -106,6 +107,23 @@ public class InputFragment extends Fragment {
|
|||
}
|
||||
switchTouchVibrationEnabled.setOnCheckedChangeListener(touch_vibration);
|
||||
|
||||
micPluggedIntoFirstController = (Switch) getView().findViewById(
|
||||
R.id.micInPort2);
|
||||
boolean micPluggedIn = sharedPreferences.getBoolean("mic_plugged_in", false);
|
||||
micPluggedIntoFirstController.setChecked(micPluggedIn);
|
||||
if (getActivity().getPackageManager().hasSystemFeature(
|
||||
"android.hardware.microphone")) {
|
||||
//Microphone is present on the device
|
||||
micPluggedIntoFirstController.setOnCheckedChangeListener(new OnCheckedChangeListener() {
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
sharedPreferences.edit().putBoolean("mic_plugged_in", isChecked).commit();
|
||||
}
|
||||
});
|
||||
}else{
|
||||
micPluggedIntoFirstController.setEnabled(false);
|
||||
}
|
||||
|
||||
|
||||
Button buttonKeycodeEditor = (Button) getView().findViewById(
|
||||
R.id.buttonKeycodeEditor);
|
||||
buttonKeycodeEditor.setOnClickListener(new View.OnClickListener() {
|
||||
|
|
|
@ -22,6 +22,8 @@ public class JNIdc
|
|||
//public static native int play(short result[],int size);
|
||||
|
||||
public static native void initControllers(boolean[] controllers);
|
||||
|
||||
public static native void setupMic(Object sip);
|
||||
|
||||
public static void show_osd() {
|
||||
JNIdc.vjoy(13, 1,0,0,0);
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
package com.reicast.emulator;
|
||||
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
import android.media.AudioFormat;
|
||||
import android.media.AudioRecord;
|
||||
import android.media.MediaRecorder;
|
||||
import android.util.Log;
|
||||
|
||||
public class SipEmulator extends Thread {
|
||||
|
||||
static final String TAG = "SipEmulator";
|
||||
|
||||
//one second of audio data in bytes
|
||||
static final int BUFFER_SIZE = 22050;
|
||||
//this needs to get set to the amount the mic normally sends per data request
|
||||
//...cant be bigger than a maple packet
|
||||
// 240 16 (or 14) bit samples
|
||||
static final int ONE_BLIP_SIZE = 480; //ALSO DEFINED IN maple_devs.h
|
||||
|
||||
private AudioRecord record;
|
||||
private ConcurrentLinkedQueue<byte[]> bytesReadBuffer;
|
||||
|
||||
private boolean continueRecording;
|
||||
private boolean firstGet;
|
||||
|
||||
/*
|
||||
16-bit PCM @ 11025 hz
|
||||
== 176.4 kbit/s
|
||||
== 22050 bytes/s
|
||||
*/
|
||||
|
||||
public SipEmulator(){
|
||||
|
||||
Log.d(TAG, "SipEmulator constructor called");
|
||||
|
||||
init();
|
||||
|
||||
}
|
||||
|
||||
private void init(){
|
||||
Log.d(TAG, "SipEmulator init called");
|
||||
|
||||
record = new AudioRecord(
|
||||
MediaRecorder.AudioSource.MIC,
|
||||
11025,
|
||||
AudioFormat.CHANNEL_IN_MONO,
|
||||
AudioFormat.ENCODING_PCM_16BIT,
|
||||
BUFFER_SIZE);
|
||||
|
||||
bytesReadBuffer = new ConcurrentLinkedQueue<byte[]>();
|
||||
|
||||
continueRecording = false;
|
||||
firstGet = true;
|
||||
}
|
||||
|
||||
public void startRecording(){
|
||||
Log.d(TAG, "SipEmulator startRecording called");
|
||||
if(continueRecording){
|
||||
return;
|
||||
}
|
||||
record.startRecording();
|
||||
continueRecording = true;
|
||||
this.start();
|
||||
}
|
||||
|
||||
public void stopRecording(){
|
||||
Log.d(TAG, "SipEmulator stopRecording called");
|
||||
continueRecording = false;
|
||||
record.stop();
|
||||
}
|
||||
|
||||
public byte[] getData(){
|
||||
//Log.d(TAG, "SipEmulator getData called");
|
||||
//Log.d(TAG, "SipEmulator getData bytesReadBuffer size: "+bytesReadBuffer.size());
|
||||
if(firstGet || bytesReadBuffer.size()>50){//50 blips is about 2 seconds!
|
||||
firstGet = false;
|
||||
return catchUp();
|
||||
}
|
||||
return bytesReadBuffer.poll();
|
||||
}
|
||||
|
||||
private byte[] catchUp(){
|
||||
Log.d(TAG, "SipEmulator catchUp");
|
||||
byte[] last = bytesReadBuffer.poll();
|
||||
bytesReadBuffer.clear();
|
||||
return last;
|
||||
}
|
||||
|
||||
public void configSomething(int what, int setting){
|
||||
Log.d(TAG, "SipEmulator configSomething called");
|
||||
|
||||
}
|
||||
|
||||
public void run() {
|
||||
Log.d(TAG, "recordThread starting");
|
||||
|
||||
while(continueRecording){
|
||||
byte[] freshData = new byte[ONE_BLIP_SIZE];
|
||||
// read blocks
|
||||
int bytesRead = record.read(freshData, 0, ONE_BLIP_SIZE);
|
||||
//Log.d(TAG, "recordThread recorded: "+bytesRead);
|
||||
if(!firstGet){
|
||||
bytesReadBuffer.add(freshData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue