Merge pull request #109 from hooby3dfx/microphone_support

Microphone support. YARLY.
This commit is contained in:
Stefanos Kornilios Mitsis Poiitidis 2014-02-02 19:00:37 -08:00
commit 1d9ec43b22
11 changed files with 422 additions and 6 deletions

View File

@ -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;
}
}

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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;
}

View File

@ -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>

View File

@ -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>

View File

@ -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() {

View File

@ -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() {

View File

@ -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);

View File

@ -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);
}
}
}
}