android PPM encoder audio library

佐手、 提交于 2019-12-01 12:23:52

I made a working example for ppm encoder class.

This is how i tested it:

1)recording the generated sound with a PC i can see on "wavepad editor" the waveform and it corresponds to what we need.

2) recording the audio output of the smartphone with a pc and analyzing the audio signal with the software "smartpropoplus" and its debug utilities, i can correctly control the PPM channels with my android app.

3) I connected the phone to a PPM receiver (DJI Lightbridge) but the signal is not correctly received. I suspect that the signal level is not the one expected by the dji device. I will wait your feedback comments, but until that moment, i suspect that i did the best that we can do with android.


NOTE: if you want to use my complete example, you need to use the file joystickView.jar in order to control the channels with a graphical joypad. this is how to use it:

1)download the jar file from this link: https://github.com/downloads/zerokol/JoystickView/joystickview.jar

2) Create a folder called "libs" on the root of your project and place the JAR file into this folder.


Now you can test my app.

These are the files of my test app:

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

file activity_test.xml

  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:baselineAligned="true"
android:orientation="vertical" >
<LinearLayout     
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
>
<TextView
    android:id="@+id/stick1VerticalLabel"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_weight="1"
    android:ems="10"
    android:text="stick1Vertical" />

<TextView
    android:id="@+id/stick1HorizontalLabel"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_weight="1"
    android:textColor="#ff0000"
    android:ems="10"
    android:text="stick1Horizontal" />

<TextView
    android:id="@+id/stick2VerticalLabel"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_weight="1"
    android:ems="10"
    android:text="stick2Vertical"  />
<TextView
    android:id="@+id/stick2HorizontalLabel"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textColor="#ff0000"
    android:layout_weight="1"
    android:ems="10"
    android:text="stick2Horizontal"  />
</LinearLayout>

<LinearLayout
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
>

    <com.zerokol.views.JoystickView
        android:id="@+id/joystickViewLeft"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent" />

    <com.zerokol.views.JoystickView
        android:id="@+id/joystickViewRight"
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:layout_gravity="end" />

</LinearLayout>




 </LinearLayout>

file Test.java

 package com.tr3ma.PPMtestProject;

 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;
 import com.zerokol.views.JoystickView;
 import com.zerokol.views.JoystickView.OnJoystickMoveListener;

 public class Test extends Activity {

 PPMEncoder ppmencoder;

 private TextView stick1VerticalLabel;
 private TextView stick1HorizontalLabel;
 private TextView stick2VerticalLabel;
 private TextView stick2HorizontalLabel;
 // Importing as others views
 private JoystickView joystickLeft;
 private JoystickView joystickRight;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_test);



    ppmencoder=new PPMEncoder(this);

    //start the generation of the signal through the speakers
    int result=ppmencoder.startGeneration();
    if (result!=0){
        //error occoured, something went wrong
        AlertDialog.Builder alert = new AlertDialog.Builder(this);
        alert.setTitle("Error");
        alert.setMessage("Error during audio signal generation. Error Number " + result);
        alert.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int whichButton) {
            }
        });
        alert.show();

    }

    stick1VerticalLabel = (TextView) findViewById(R.id.stick1VerticalLabel);
    stick1HorizontalLabel = (TextView) findViewById(R.id.stick1HorizontalLabel);
    stick2VerticalLabel = (TextView) findViewById(R.id.stick2VerticalLabel);
    stick2HorizontalLabel = (TextView) findViewById(R.id.stick2HorizontalLabel);
    // referring as others views
    joystickLeft = (JoystickView) findViewById(R.id.joystickViewLeft);
    joystickRight = (JoystickView) findViewById(R.id.joystickViewRight);

    // Listener of events, it'll return the angle in graus and power in percents
    // return to the direction of the moviment
    joystickLeft.setOnJoystickMoveListener(new OnJoystickMoveListener() {
         @Override
         public void onValueChanged(int angle, int power, int direction) {

             //scompose the vector
             float stickVertical=(float) Math.sin((Math.PI/180) * angle)*power; //values between +100 and -100
             stickVertical=stickVertical+(float)100; //values between 0 and 200
             stickVertical=(float)stickVertical*(float)((float)255/(float)200); //values between 0 and 255


             float stickHorizontal=(float) Math.cos((Math.PI/180) * angle)*power; //values between +100 and -100
             stickHorizontal=stickHorizontal+(float)100; //values between 0 and 200
             stickHorizontal=stickHorizontal*(float)((float)255/(float)200); //values between 0 and 255

             stick1VerticalLabel.setText("channel1:" + String.valueOf(stickVertical));
             stick1HorizontalLabel.setText("channel2:" + String.valueOf(stickHorizontal));

             ppmencoder.setChannelValue(1, stickVertical);
             ppmencoder.setChannelValue(2, stickHorizontal);


         }
    }, JoystickView.DEFAULT_LOOP_INTERVAL);

    joystickRight.setOnJoystickMoveListener(new OnJoystickMoveListener() {
        @Override
        public void onValueChanged(int angle, int power, int direction) {

         //scompose the vector
            //scompose the vector
         float stickVertical=(float) Math.sin((Math.PI/180) * angle)*power; //values between +100 and -100
         stickVertical=stickVertical+(float)100; //values between 0 and 200
         stickVertical=(float)stickVertical*(float)((float)255/(float)200); //values between 0 and 255


         float stickHorizontal=(float) Math.cos((Math.PI/180) * angle)*power; //values between +100 and -100
         stickHorizontal=stickHorizontal+(float)100; //values between 0 and 200
         stickHorizontal=stickHorizontal*(float)((float)255/(float)200); //values between 0 and 255

         stick2VerticalLabel.setText("channel3:" + String.valueOf(stickVertical));
         stick2HorizontalLabel.setText("channel4:" + String.valueOf(stickHorizontal));

         ppmencoder.setChannelValue(3, stickVertical);
         ppmencoder.setChannelValue(4, stickHorizontal);


        }
   }, JoystickView.DEFAULT_LOOP_INTERVAL);


}

@Override
protected void onDestroy() {
    super.onDestroy();
    int result=ppmencoder.stopGeneration();
    if (result!=0){
        AlertDialog.Builder alert = new AlertDialog.Builder(this);
        alert.setTitle("Error");
        alert.setMessage("Error while stopping the audio generation. Error number " + result);
        alert.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int whichButton) {
            }
        });
        alert.show();
    }
}
 }

file PPMEncoder.java (this is the class requested on the original question)

 package com.tr3ma.PPMtestProject;

 import java.util.ArrayList;

 import android.content.Context;
 import android.media.AudioFormat;
 import android.media.AudioManager;
 import android.media.AudioTrack;
 import android.os.AsyncTask;

 public class PPMEncoder
 {
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;

private ArrayList<Float> channelValues;

AudioManager audioManager;
StreamPPMSignalTask streamPPMSignalTask;

private boolean started;

public PPMEncoder(Context context)
{
    audioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);

    //set volume to max
    //audioManager=(AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    int tmpVol = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);    
    audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, tmpVol, 0);



    channelValues = new ArrayList<Float>(8);
    for (int i = 0; i < 8; i++) {
        channelValues.add((float)0.68181818);
    }
}

public int startGeneration()
{
    try {


        audioBufferSize = AudioTrack.getMinBufferSize(SAMPLE_RATE,
                AudioFormat.CHANNEL_OUT_MONO,
                AudioFormat.ENCODING_PCM_16BIT)*2;

        if (audioBufferSize<=0 ) return -2;

        started = true;

        streamPPMSignalTask = new StreamPPMSignalTask();
        streamPPMSignalTask.execute();
        return 0;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return -1;
}

public int stopGeneration()
{
    try {
        started = false;

        streamPPMSignalTask.cancel(true);
        streamPPMSignalTask = null;
        return 0;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return -1;
}


private int timeToSamples(float time)
{
    //time is expressed in milliseconds
    return (int)Math.round(time * 0.001 * SAMPLE_RATE);
}

public void setChannelValue(int channel, float value)
{
    channelValues.set(channel - 1, (float)0.68181818+(float)1.0 * ((float)value/(float)255));
}

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 = AudioTrack.getMinBufferSize(SAMPLE_RATE,
                AudioFormat.CHANNEL_OUT_MONO,
                AudioFormat.ENCODING_PCM_16BIT) * 2;

        if (audioBufferSize<=0 ) return -2;

        started=false;
        stopGeneration();
        startGeneration();

        //frame=new byte[streamBufferSize];
        return 0;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return -1;
}


public class StreamPPMSignalTask extends AsyncTask<Void, Double, Void>
{
    @Override
    protected Void doInBackground(Void... arg0) {
        AudioTrack ppmTrack = new AudioTrack(AudioManager.STREAM_MUSIC, SAMPLE_RATE, AudioFormat.CHANNEL_OUT_MONO,
                AudioFormat.ENCODING_PCM_16BIT, audioBufferSize, AudioTrack.MODE_STREAM);

        //set volume of audioplayer to max
        ppmTrack.setStereoVolume((float) 1.0, (float) 1.0);

        if (ppmTrack.getPlayState() != AudioTrack.PLAYSTATE_PLAYING) {
            ppmTrack.play();
        }

        //feed the speakers with our audio, by continuously send the PPM frame
        int tempBound;
        while (started) {
            try {
                short[] frame = new short[ppmFrameBufferSize];

                int i = 0;
                tempBound = i + timeToSamples((float)0.3);
                for (;i < tempBound; i += 1) {
                    frame[i] = Short.MIN_VALUE;
                }

                for (int channel = 0; channel < 8; channel++) {
                    tempBound = i + timeToSamples(channelValues.get(channel));
                    for (;i < tempBound; i += 1) {
                        frame[i] = Short.MAX_VALUE;
                    }

                    tempBound= i + timeToSamples((float)0.3);
                    for (;i < tempBound; i += 1) {
                        frame[i] = Short.MIN_VALUE;
                    }
                }

                for (;i < frame.length; i += 1) {
                    frame[i] = Short.MAX_VALUE;
                }

                //send the frame
                ppmTrack.write(frame, 0, frame.length);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}
 }

End notes:

1) as you can see the 2 joysticks moves only 4 channels, but it is clear that you can add another 2 joystick controls on the activity in order to move all 8 channels.

2) credit goes to this site where you can see how it is made the joystickControl http://www.zerokol.com/2012/03/joystickview-custom-android-view-to.html in case you want to customize it. I wish to do it but i had no time. today i spent the entire day to write this post.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!