AndroidにオーディオPPM(パルス位置変調)を実装する必要があります
参照: http://en.wikipedia.org/wiki/Pulse-position_modulation
スマートフォンのオーディオマイク入力を使ってPPMを受信したい。
ラジコン、ドローンなどの無線は、一般に PPM 出力を備えています。トランスミッター (および PC フライト シミュレーター) には、一般に PPM 入力があります。
あなたがこの仕事で私を助けてくれるかどうか知りたいです。
あなたがここで読むことができるように、私はまだppmエンコーダークラスを書いています:アンドロイドPPMエンコーダーオーディオライブラリ
これらは、いくつかの開始ドキュメントとツールです。
1) smartpropplus は、PPM オーディオを受信してデコードする Windows ソフトウェアですhttp://sourceforge.net/p/smartpropoplus/code/HEAD/tree/SPP4/
2) これは PPM の構造です: http://www.aerodesign.de/peter/2000/PCM/PCM_PPM_eng.html#Anker144123
3) これは信号がどのように構成されているかを説明する簡単な画像です: http://www.aerodesign.de/peter/2000/PCM/frame_ppm.gif
4) オシロスコープによる ppm 信号測定: http://www.andrewhazelden.com/blog/2011/08/analyzing-rc-radio-ppm-signals/
編集: あなたのサポートを待っている間、私はサンプルアプリを書き始めました:
これはクラスPPMdecoder.javaで、信号のデコードとキャリブレーションフェーズが欠落しています....しかし、マイクからの信号を正しく取得することができます
注:コード内の「//To Be Done」コメントは、この質問を解決するために不足しているコードを示しています。
クラス PPMdecoder.java
package com.tr3ma.PPMtestProject;
import java.util.ArrayList;
import android.content.Context;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.AsyncTask;
import android.util.Log;
public class PPMDecoder {
public int SAMPLE_RATE = 44100;
public int ppmFrameBufferSize = (int)(SAMPLE_RATE * 0.0225); // 22KHz * 22,5ms that it is the duration of a frame ppm
public int audioBufferSize;
int calibrationStatus=0;
private ArrayList<Float> channelValues;
AudioManager audioManager;
RecordAudio receivePPMSignalTask;
private boolean started;
long elapsedTimeSinceLastPublish;
long lastPublishMilliseconds;
IAsyncFetchListener fetchListener = null;
public void setListener(IAsyncFetchListener listener) {
this.fetchListener = listener;
}
public PPMDecoder(Context context)
{
audioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);
channelValues = new ArrayList<Float>(8);
for (int i = 0; i < 8; i++) {
channelValues.add(null);
}
}
public int startDecoding()
{
try {
audioBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT)*2;
if (audioBufferSize<=0 ) return -2;
started = true;
receivePPMSignalTask = new RecordAudio();
receivePPMSignalTask.execute();
return 0;
} catch (Exception e) {
e.printStackTrace();
}
return -1;
}
public int stopDecoding()
{
try {
started = false;
receivePPMSignalTask.cancel(true);
receivePPMSignalTask = null;
return 0;
} catch (Exception e) {
e.printStackTrace();
}
return -1;
}
private float samplesToTime(int samples)
{
return (((float)samples/( (float)SAMPLE_RATE)) * (float)1000); //time is expressed in milliseconds
}
public int getChannelValue(int channel)
{
float tmpVal = channelValues.get(channel-1); //get the value
tmpVal = (((tmpVal-(float)0.68181818) * (float)255 ) / (float)1.0); //convert to value between 0 and 255
if (tmpVal<0) return 0;
if (tmpVal>255) return 255;
return (int)Math.round( tmpVal);
}
public int setSamplingRate(int freq) {
//we can change the sampling frequency in case the default one is not supported
try {
SAMPLE_RATE=freq;
ppmFrameBufferSize = (int)(SAMPLE_RATE* 0.0225); // 22KHz * 22,5ms
audioBufferSize = AudioRecord.getMinBufferSize(SAMPLE_RATE,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT)*2;
if (audioBufferSize<=0 ) return -2;
started=false;
stopDecoding();
startDecoding();
//frame=new byte[streamBufferSize];
return 0;
} catch (Exception e) {
e.printStackTrace();
}
return -1;
}
public class RecordAudio extends AsyncTask<Void, Void, Throwable> {
@Override
protected Throwable doInBackground(Void... arg0) {
try {
AudioRecord audioRecord = new AudioRecord( MediaRecorder.AudioSource.MIC, SAMPLE_RATE,
AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, audioBufferSize);
short[] buffer = new short[audioBufferSize];
//final double[] fftData = new double[audioBufferSize];
//RealDoubleFFT fft = new RealDoubleFFT(audioBufferSize);
audioRecord.startRecording();
lastPublishMilliseconds=System.currentTimeMillis();
while (started) {
//Log.e("audioRecord Time1 before read", "" + System.currentTimeMillis() );
int bufferReadResult = audioRecord.read(buffer, 0,audioBufferSize);
//Log.e("audioRecord Time2 after read", "" + System.currentTimeMillis() );
//Log.e("audioRecord bufferReadResult", "" + bufferReadResult );
if(AudioRecord.ERROR_INVALID_OPERATION == bufferReadResult){
//Log.e("audioRecord ErrorInvalidOperation", "Error " + System.currentTimeMillis() );
} else {
//process the data
if (calibrationStatus>0){
//Log.w(TAG, "WE are calibrating");
//To Be Done................................
continue;
}
//decode
//To Be Done.......................
//each 100ms publish the channels value
elapsedTimeSinceLastPublish=System.currentTimeMillis()-lastPublishMilliseconds;
if (elapsedTimeSinceLastPublish>100){
publishProgress();
lastPublishMilliseconds=System.currentTimeMillis();
}
}
}
audioRecord.stop();
audioRecord.release();
audioRecord=null;
} catch (Throwable t) {
t.printStackTrace();
Log.e("audioRecord", "Recording Failed");
return t;
}
return null;
} //fine di doInBackground
@Override
protected void onProgressUpdate(Void... arg0) {
//generate interupt in the father thread
if (fetchListener != null) fetchListener.update(channelValues);
}
protected void onPostExecute(Throwable result){
if (result==null){
return;
}
//if we arrive here, report error "Sound Recorder Busy" and reset the acquisition
//To be done...
}
} //Fine Classe RecordAudio (AsyncTask)
}
これはインターフェイス IAsyncFetchListener.java であり、デコーダーがチャンネル値で UI を定期的に更新できるようにするために、UI スレッドでリスナーを作成するために必要でした。
package com.tr3ma.PPMtestProject;
import java.util.ArrayList;
import java.util.EventListener;
public interface IAsyncFetchListener extends EventListener {
void update(ArrayList<Float> channelValues );
}
これは、サンプル アクティビティ クラス Test.java です。
package com.tr3ma.PPMtestProject;
import java.util.ArrayList;
import android.os.Bundle;
import android.widget.TextView;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import com.tr3ma.PPMtestProject.R;
public class Test extends Activity {
PPMDecoder ppmdecoder;
....
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
ppmdecoder=new PPMDecoder(this);
ppmdecoder.setListener(new IAsyncFetchListener() {
public void update(ArrayList<Float> channelValues ) {
// do something with channelValues
//To Be Done................................
}
});
//start to receive the signal through the microphone
int result=ppmdecoder.startDecoding();
if (result!=0){
//error occoured, something went wrong
AlertDialog.Builder alert = new AlertDialog.Builder(this);
alert.setTitle("Error");
alert.setMessage("Error during audio signal receiving. Error Number " + result);
alert.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
}
});
alert.show();
}
.....
}
@Override
protected void onDestroy() {
super.onDestroy();
int result=ppmdecoder.stopDecoding();
if (result!=0){
AlertDialog.Builder alert = new AlertDialog.Builder(this);
alert.setTitle("Error");
alert.setMessage("Error while stopping the audio receiving. Error number " + result);
alert.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
}
});
alert.show();
}
....
}
}
これは AndroidManifest.xml です
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tr3ma.PPMtestProject"
android:versionCode="1"
android:versionName="1.0" >
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="17" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.tr3ma.PPMtestProject.Test"
android:label="@string/app_name"
android:screenOrientation="landscape"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>