Android PPM编码器音频库

2023-12-23

我需要在 Android 上实现音频 PPM(脉冲位置调制)

参考:http://en.wikipedia.org/wiki/Pulse-position_modulation http://en.wikipedia.org/wiki/Pulse-position_modulation

我想从智能手机的音频输出输出 PPM。 最终的目标是创建一个用于无线电控制的操纵杆。但是这个库可能有很多未来的用途(follow me、lightbridge 等等)。 无线电通常具有 PPM 输出。发射机(和 PC 飞行模拟器)通常具有 PPM 输入。 我的目标是用 Android 设备替换收音机。 我想知道是否有一些代码可以使用,或者我应该从头开始?

编辑:我找到了一些从哪里开始

1)smartpropplus是一个windows软件,接收PPM音频并解码http://sourceforge.net/p/smartpropoplus/code/HEAD/tree/SPP4/ http://sourceforge.net/p/smartpropoplus/code/HEAD/tree/SPP4/

2)PPM 的结构如下:http://www.aerodesign.de/peter/2000/PCM/PCM_PPM_eng.html#Anker144123 http://www.aerodesign.de/peter/2000/PCM/PCM_PPM_eng.html#Anker144123

3)这是一个简单的图像,解释了信号的结构:http://www.aerodesign.de/peter/2000/PCM/frame_ppm.gif http://www.aerodesign.de/peter/2000/PCM/frame_ppm.gif

我计算出以 22000Hz 采样音频信号足以为每个通道实现良好的分辨率(每个通道 22 个步长)

注意:如果您有兴趣接收 ppm 音频信号,您需要 android ppm 解码器类,您可以在这里找到:Android PPM解码器音频库 https://stackoverflow.com/questions/34653684/android-ppm-decoder-audio-library


我为 ppm 编码器类制作了一个工作示例。

这就是我测试的方法:

1)用PC记录生成的声音,我可以在“wavepad编辑器”上看到波形,它符合我们的需要。

2)用电脑记录智能手机的音频输出,并使用软件“smartpropoplus”及其调试实用程序分析音频信号,我可以使用我的Android应用程序正确控制PPM通道。

3) 我将手机连接至 PPM 接收器(DJI Lightbridge),但未正确接收信号。我怀疑信号电平不是 dji 设备预期的信号电平。我会等待你的反馈意见,但在那一刻之前,我怀疑我已经在 Android 上做到了最好。


笔记: 如果您想使用我的完整示例,您需要使用文件 JoystickView.jar 以便通过图形手柄控制通道。 这是如何使用它:

1)从此链接下载jar文件:https://github.com/downloads/zerokol/JoystickView/joystickview.jar https://github.com/downloads/zerokol/JoystickView/joystickview.jar

2) 在项目根目录下创建一个名为“libs”的文件夹,并将 JAR 文件放入该文件夹中。


现在您可以测试我的应用程序。

这些是我的测试应用程序的文件:

文件 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>

文件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>

文件测试.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();
    }
}
 }

文件 PPMEncoder.java (这是原始问题所请求的类)

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

尾注:

1) 正如您所看到的,2 个操纵杆仅移动 4 个通道,但很明显,您可以在活动上添加另外 2 个操纵杆控件,以便移动所有 8 个通道。

2) 版​​权归此网站所有,您可以在其中看到如何制作操纵杆控制http://www.zerokol.com/2012/03/joystickview-custom-android-view-to.html http://www.zerokol.com/2012/03/joystickview-custom-android-view-to.html如果您想定制它。我想这样做,但我没有时间。今天我花了一整天的时间来写这篇文章。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android PPM编码器音频库 的相关文章

  • 使用项目中的波形文件

    我目前只能通过将波形文件放在已编译的 exe 旁边来播放背景声音 但我实际上想要一个包含波形文件的静态可执行文件 这在Delphi XE2中可能吗 这是我的代码 SndPlaySound Raw wav SND ASYNC or SND L
  • 如何解决:无法解析:com.mapbox.mapboxsdk:mapbox-android-sdk:9.5.0

    我在Android studio中尝试使用mapbox时遇到这个问题无法解析 com mapbox mapboxsdk mapbox android sdk 9 5 0 问题是什么 我的 build gradle 依赖项 dependenc
  • Android版本App更新代码

    我读到如果我们想更新Google Play中的应用程序 版本代码应该高于以前的apk文件 我有一个版本代码为 20 且版本名称为 1 0 的应用程序 那么要更新app 应该如何增加版本号呢 应该增加10吗 或者仅仅 1 就足够了 即版本代码
  • Android 从 C++ 端播放原始音频

    我需要能够在 Android 系统的 C 端以自定义文件格式传输音频 我正在致力于移植自定义媒体播放器 并且需要能够打开自定义文件并从中传输音频 这很重要 因为我认为从性能角度来看将整个播放器移植到 JAVA 是不可行的 并且通过 JNI
  • Auto-value-gson出现接口错误,注册一个InstanceCreator?

    我有一个如下所示的接口类 public interface Species String name And a Human实现的类 AutoValue使用类型适配器 AutoValue public abstract class Human
  • 如何使用数据绑定将点击侦听器设置为 LinearLayout

    我目前正在尝试将点击侦听器设置为LinearLayout查看在 xml使用数据绑定的布局文件 我已经设法让它在其他视图上很好地工作 比如Button or TextView 但由于某种原因 它不能与LinearLayout 这是我尝试的基本
  • Android中如何使用JNI获取设备ID?

    我想从 c 获取 IMEIJNI 我使用下面的代码 但是遇到了未能获取的错误cls 它总是返回NULL 我检查了环境和上下文 它们都没有问题 为什么我不能得到Context班级 我在网上搜索了一下 有人说我们应该使用java lang Ob
  • 在android中从JSON生成listview

    我对 Android 完全陌生 目前正在尝试从从我的服务器中提取的 JSON 数组生成列表视图 我已经阅读了很多教程 但没有运气 有一种独特的方法可以做到这一点 请您指出一些适合开始的资源 我读过了this http www josecgo
  • SQLite FTS4 使用特殊字符进行搜索

    我有一个 Android 应用程序 它使用 FTS4 虚拟表在 SQLite 数据库中搜索数据 它工作正常 但是当表中的数据包含特殊字符 如 或 时 SQLite MATCH 函数不会给出任何结果 我现在迷路了 谢谢 注意 默认的分词器真的
  • 更新到 Kotlin 1.3.30 后出现“未解析的引用:Parcelize”

    我使用 Kotlin 1 3 21 很长时间了kotlin android extensions插件长期处于实验模式 今天我通过升级版本切换到 Kotlin 1 3 30 现在无论我使用什么 Parcelize注释我看到错误 Unresol
  • 是否可以将自定义属性添加到 Android 资源的样式中?

    我在我的项目中使用视图流组件 它允许开发人员覆盖一些属性 例如
  • 在 /dev/input/eventX 中写入事件需要哪些命令?

    我正在开发一个android需要将触摸事件发送到 dev input eventX 的应用程序 我知道C执行此类操作的代码结构如下 struct input event struct timeval time unsigned short
  • 将 android 蓝牙客户端套接字连接到 ubuntu 服务器套接字时出现问题

    我正在编写一个 Android 应用程序 它应该通过蓝牙与服务器交换数据 服务器端位于运行 Ubuntu 的 PC 上 使用 bluez 库 用 C 或 C 编写 当我尝试连接到 PC 上的服务器套接字时 我的 Android 应用程序失败
  • 在 Android 中跨单元测试和仪器测试共享资源

    现在谷歌已经添加了实验单元测试支持 http tools android com tech docs unit testing support 如何在单元测试和仪器测试之间共享资源 例如 假设我有一个TestUtils java我希望在单元
  • 警报对话框中的 Webview 不显示内容

    我正在开发一个 Android 应用程序 我需要在网络视图和警报对话框上显示一个网站 该站点显示在网络视图中 但不显示在警报对话框中 到目前为止 这是我的代码 WebView WebView myWebView WebView v find
  • Android Gradle 问题 - Flutter / Dart

    我的 Gradle 同步有问题 我使用 IntelliJ 和 Android Studio 构建 Flutter Dart 应用程序 我添加了 2 个新的依赖项 现在 Gradle 出现了问题 在 Android Studio 中一切正常
  • Jetpack Compose 中复选框中的透明复选标记

    在我的 Compose 应用程序中 我需要创建一个圆形复选框 我已经通过下面的代码实现了这一点 Composable fun CircleCheckBox isChecked Boolean modifier Modifier Modifi
  • Android S8+ 警告消息“不支持当前的显示尺寸设置,可能会出现意外行为”

    我在 Samsung S8 Android 7 中收到此警告消息 APP NAME 不支持当前的显示尺寸设置 可能会 行为出乎意料 它意味着什么以及如何删除它 谢谢 通过添加解决supports screens 机器人 xlargeScre
  • 如何在android中安装和使用couch db

    我应该如何在 android 中安装和使用 couch Db 我的意思是本地沙发数据库 我可以在平板电脑和模拟器中使用它 为此我必须遵循哪些步骤 我目前正在开发一个使用它的项目 有两种选择 1 couchbase android 是的 co
  • 在线性布局内的 ScrollView 内并排对齐 TextView

    我有一个带有滚动视图的线性布局 我想保留它的当前格式 但只需将 textView2a 和 textView3a 并排放置 而不会破坏我当前的布局格式 我已经包含了我最近的尝试 但它们似乎不正确 提前致谢 Java菜鸟 当前有效的 XML

随机推荐