- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我需要在android上实现音频PPM(脉冲位置调制)
引用:http://en.wikipedia.org/wiki/Pulse-position_modulation
我想从智能手机的音频输出中输出 PPM。最后的范围是为 radio 控制创建一个操纵杆。但是这个库可能有很多 future 的用途(follow me、lightbridge 等)。 radio 通常具有 PPM 输出。发射器(和 PC 飞行模拟器)通常具有 PPM 输入。我的范围是用安卓设备替换 radio 。我想知道是否有一些代码可供使用,还是我应该从头开始?
编辑:我找到了一些起点
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
我计算出以 22000Hz 采样音频信号足以为每个 channel 实现良好的分辨率(每个 channel 22 步)
注意:如果您有兴趣接收 ppm 音频信号,您需要 android ppm 解码器类,您可以在此处找到:android PPM decoder audio library
最佳答案
我是这样测试的:
1)用PC录制生成的声音我可以在“wavepad editor”上看到波形,它符合我们需要的。
2) 用电脑录制智能手机的音频输出,并用“smartpropoplus”软件及其调试工具分析音频信号,我可以用我的安卓应用程序正确控制 PPM channel 。
3) 我将手机连接到 PPM 接收器 (DJI Lightbridge),但没有正确接收到信号。我怀疑信号电平不是 dji 设备预期的信号电平。我会等待您的反馈意见,但在那一刻之前,我怀疑我已经尽了我们在 android 方面所能做到的最好。
注意:如果你想使用我的完整示例,你需要使用文件 joystickView.jar 来控制带有图形游戏 handle 的 channel 。这是如何使用它:
1) 从这个链接下载 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 个 channel ,但很明显您可以在 Activity 上添加另外 2 个操纵杆控件以移动所有 8 个 channel 。
2) 归功于这个网站,在那里你可以看到它是如何制作的 joystickControl http://www.zerokol.com/2012/03/joystickview-custom-android-view-to.html如果您想自定义它。我想做,但我没有时间。今天我花了一整天的时间来写这篇文章。
关于android PPM编码器音频库,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29715866/
我有课 class Header { @FCBTag(type="type1") --My custom annotation int a = "valueA"; @FCBTa
我一直在使用 Apache MINA 并正在学习 Netty。我过去使用过 MINA 累积编码器/解码器,我有兴趣看看 Netty 是否有类似的功能。我查看了 API,但没有看到任何内容。 最佳答案
我有一组使用 wsdl2java (Axis 1.4) 创建的类,我正在寻找一种方法来解码和编码来自/到字符串和对象的数据。我已经编写了一个 JAXB 解码器,它适用于我们的一些较新的内部对象,因为我
在我的自定义类 WLNetworkClient 中,我必须实现这样的方法: required init(coder aDecoder: NSCoder) { fatalError("init(
基于 transformer 的编码器-解码器模型是 表征学习 和 模型架构 这两个领域多年研究成果的结晶。本文简要介绍了神经编码器-解码器模型的历史,更多背景知识,建议读者阅读由 Seba
在使用 FFMPEG android java 库时发生以下异常的视频播放速度(使视频变慢)。 [aac @ 0x416c26f0] The encoder 'aac' is experimental
我正在从一个程序运行 ffmpeg,我们自己构建了 ffmpeg(我们没有使用包管理器或预构建的东西安装它)。 这是构建的命令: 2020-07-31 12:14:11.942 INFO ffmpeg
许多基于LSTM的seq2seq编码器-解码器架构教程(例如英法翻译),将模型定义如下: encoder_inputs = Input(shape=(None,)) en_x= Embedding(
如何覆盖使用 marshmallow 的 JSON 编码器库,以便它可以序列化 Decimal字段?我想我可以通过覆盖 json_module 来做到这一点在基地Schema或 Meta课,但我不知道
在我的 Grails 2.5.0 应用程序中,我使用了一组自定义 JSON 编码器来严格控制由我的 REST 端点返回的 JSON 格式。目前我在这样的服务中注册这些编码器 class Marshal
我需要多个自定义 JSON 编码器,因为我想针对不同的目的以不同的方式进行编码。我知道如何使用以下方法设置自定义编码器应用程序: JSON.registerObjectMarshaller(MyCla
查看文档,它是这样说的: https://netty.io/4.0/api/io/netty/channel/ChannelPipeline.html A user is supposed to ha
我希望为以下案例类提供 JSON 编码器: import io.circe.generic.extras.Configuration final case class Hello[T]( so
我正在构建一个 JPEG 图像编码器。就目前情况而言,为了对图像进行编码,用户输入他们希望编码的文件的名称以及由此创建的文件的名称。 我希望用户能够在命令行中设置编码的质量。我尝试重命名 new Jp
我有想要在 webview 中显示的 html 文本。 如specification ,数据必须经过 URI 转义。所以我尝试使用 URLEncoder.encode() 函数,但这对我没有帮助,因为
我目前正在自己实现 PNG 滤镜。我正在使用神经网络尝试创建比当前现有的 PNG 过滤器更好的预测: 0 - 无 1 - 子 2 - 向上 3 - 平均 4 - 派斯 5 - 我的实现(使用神经网
让我们假设我们有与 Schema 一致的 XML 和带有一些公共(public)字段的 Java 类: public clas
在我的 Java 应用程序中,我正在寻找 URLEncoder.encode(String s, String enc) 的流媒体版本.我想使用“application/x-www-form-urle
我确实有一个对象层次结构,我想使用“import javax.xml.bind.Marshaller”将其从 Java 对象转换为 xml。我的java类文件被编码在“Cp1252”中,我无法更改它。
使用 Netty 4.0.27 和 Java 1.8.0_20 所以我试图通过构建一个简单的聊天服务器(我猜是典型的网络教程程序?)来了解 Netty 的工作原理。设计我自己的简单协议(protoco
我是一名优秀的程序员,十分优秀!