Unverified Commit cf7e6107 authored by Gus Grubba's avatar Gus Grubba Committed by GitHub

Merge pull request #7131 from MatejFranceskin/pr-taisync-android

PR Taisync support for Android
parents 36fcd714 868b12c9
......@@ -36,6 +36,7 @@ linux {
DEFINES += __android__
DEFINES += __STDC_LIMIT_MACROS
DEFINES += QGC_ENABLE_BLUETOOTH
DEFINES += QGC_GST_TAISYNC_ENABLED
target.path = $$DESTDIR
equals(ANDROID_TARGET_ARCH, x86) {
CONFIG += Androidx86Build
......
......@@ -15,7 +15,8 @@ OTHER_FILES += \
$$PWD/android/src/com/hoho/android/usbserial/driver/UsbSerialProber.java \
$$PWD/android/src/com/hoho/android/usbserial/driver/UsbSerialRuntimeException.java \
$$PWD/android/src/org/mavlink/qgroundcontrol/QGCActivity.java \
$$PWD/android/src/org/mavlink/qgroundcontrol/UsbIoManager.java
$$PWD/android/src/org/mavlink/qgroundcontrol/UsbIoManager.java \
$$PWD/android/src/org/mavlink/qgroundcontrol/TaiSync.java
DISTFILES += \
$$PWD/android/gradle/wrapper/gradle-wrapper.jar \
......
......@@ -7,10 +7,12 @@
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"/>
<action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED"/>
<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"/>
</intent-filter>
<meta-data android:resource="@xml/device_filter" android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"/>
<meta-data android:resource="@xml/device_filter" android:name="android.hardware.usb.action.USB_DEVICE_DETACHED"/>
<meta-data android:resource="@xml/device_filter" android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"/>
<!-- Rest of Standard Manifest -->
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
......@@ -60,13 +62,14 @@
<!-- Support devices without USB host mode since there are other connection types -->
<uses-feature android:name="android.hardware.usb.host" android:required="false"/>
<!-- Support devices without Bluetooth since there are other connection types -->
<uses-feature android:name="android.hardware.bluetooth" android:required="false"/>
<!-- Support devices without Bluetooth since there are other connection types -->
<uses-feature android:name="android.hardware.bluetooth" android:required="false"/>
<!-- Support devices that don't have location services -->
<!-- Support devices that don't have location services -->
<uses-feature android:name="android.hardware.location.gps" android:required="false" />
<uses-feature android:name="android.hardware.location.network" android:required="false"/>
<uses-feature android:name="android.hardware.location" android:required="false"/>
<uses-feature android:name="android.hardware.location.network" android:required="false"/>
<uses-feature android:name="android.hardware.location" android:required="false"/>
<uses-feature android:name="android.hardware.usb.accessory"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" />
......
......@@ -2,5 +2,6 @@
<resources>
<!-- Allow anything connected -->
<usb-device />
<usb-accessory model="android.usbaoa" manufacturer="taisync" version="1.0"/>
</resources>
......@@ -29,11 +29,14 @@ package org.mavlink.qgroundcontrol;
//
////////////////////////////////////////////////////////////////////////////////////////////
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.Timer;
import java.util.TimerTask;
import java.io.IOException;
import android.app.Activity;
......@@ -42,12 +45,16 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.*;
import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import android.widget.Toast;
import android.util.Log;
import android.os.PowerManager;
import android.view.WindowManager;
import android.os.Bundle;
import android.app.PendingIntent;
import android.view.WindowManager;
import com.hoho.android.usbserial.driver.*;
import org.qtproject.qt5.android.bindings.QtActivity;
......@@ -63,8 +70,10 @@ public class QGCActivity extends QtActivity
private static HashMap<Integer, Integer> _userDataHashByDeviceId;
private static final String TAG = "QGC_QGCActivity";
private static PowerManager.WakeLock _wakeLock;
private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION";
// private static final String ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION";
private static final String ACTION_USB_PERMISSION = "org.mavlink.qgroundcontrol.action.USB_PERMISSION";
private static PendingIntent _usbPermissionIntent = null;
private TaiSync taiSync = null;
public static Context m_context;
......@@ -87,6 +96,26 @@ public class QGCActivity extends QtActivity
}
};
private final BroadcastReceiver mOpenAccessoryReceiver =
new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ACTION_USB_PERMISSION.equals(action)) {
UsbAccessory accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
openAccessory(accessory);
}
} else if( UsbManager.ACTION_USB_ACCESSORY_DETACHED.equals(action)) {
UsbAccessory accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
if (accessory != null) {
closeAccessory(accessory);
}
}
}
};
private static UsbSerialDriver _findDriverByDeviceId(int deviceId) {
for (UsbSerialDriver driver: _drivers) {
if (driver.getDevice().getDeviceId() == deviceId) {
......@@ -169,7 +198,7 @@ public class QGCActivity extends QtActivity
_usbManager = (UsbManager)_instance.getSystemService(Context.USB_SERVICE);
// Register for USB Detach and USB Permission intent
// Register for USB Detach and USB Permission intent
IntentFilter filter = new IntentFilter();
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
filter.addAction(ACTION_USB_PERMISSION);
......@@ -177,11 +206,24 @@ public class QGCActivity extends QtActivity
// Create intent for usb permission request
_usbPermissionIntent = PendingIntent.getBroadcast(_instance, 0, new Intent(ACTION_USB_PERMISSION), 0);
try {
taiSync = new TaiSync();
IntentFilter accessoryFilter = new IntentFilter(ACTION_USB_PERMISSION);
filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED);
registerReceiver(mOpenAccessoryReceiver, accessoryFilter);
probeAccessories();
} catch(Exception e) {
Log.e(TAG, "Exception: " + e);
}
}
@Override
protected void onDestroy()
{
unregisterReceiver(mOpenAccessoryReceiver);
try {
if(_wakeLock != null) {
_wakeLock.release();
......@@ -611,5 +653,61 @@ public class QGCActivity extends QtActivity
else
return connectL.getFileDescriptor();
}
UsbAccessory openUsbAccessory = null;
Object openAccessoryLock = new Object();
private void openAccessory(UsbAccessory usbAccessory)
{
Log.i(TAG, "openAccessory: " + usbAccessory.getSerial());
try {
synchronized(openAccessoryLock) {
if ((openUsbAccessory != null && !taiSync.isRunning()) || openUsbAccessory == null) {
openUsbAccessory = usbAccessory;
taiSync.open(_usbManager.openAccessory(usbAccessory));
}
}
} catch (IOException e) {
Log.e(TAG, "openAccessory exception: " + e);
taiSync.close();
closeAccessory(openUsbAccessory);
}
}
private void closeAccessory(UsbAccessory usbAccessory)
{
Log.i(TAG, "closeAccessory");
synchronized(openAccessoryLock) {
if (openUsbAccessory != null && usbAccessory == openUsbAccessory && taiSync.isRunning()) {
taiSync.close();
openUsbAccessory = null;
}
}
}
private void probeAccessories()
{
final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0);
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run()
{
// Log.i(TAG, "probeAccessories");
UsbAccessory[] accessories = _usbManager.getAccessoryList();
if (accessories != null) {
for (UsbAccessory usbAccessory : accessories) {
if (_usbManager.hasPermission(usbAccessory)) {
openAccessory(usbAccessory);
} else {
Log.i(TAG, "requestPermission");
_usbManager.requestPermission(usbAccessory, pendingIntent);
}
}
}
}
}, 0, 3000);
}
}
package org.mavlink.qgroundcontrol;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Socket;
import java.net.InetAddress;
import android.os.ParcelFileDescriptor;
import android.util.Log;
public class TaiSync
{
private static final int HEADER_SIZE = 0x1C;
private static final byte PROTOCOL_REQUEST_CONNECTION = 0x00;
private static final byte PROTOCOL_VERSION = 0x01;
private static final byte PROTOCOL_CHANNEL = 0x02;
private static final byte PROTOCOL_DATA = 0x03;
private static final int VIDEO_PORT = 5600;
private static final int TAISYNC_VIDEO_PORT = 8000;
private static final int TAISYNC_SETTINGS_PORT = 8200;
private static final int TAISYNC_TELEMETRY_PORT = 8400;
private Object runLock;
private boolean running = false;
private DatagramSocket udpSocket = null;
private Socket tcpSettingsSocket = null;
private InputStream settingsInStream = null;
private OutputStream settingsOutStream = null;
private Socket tcpTelemetrySocket = null;
private InputStream telemetryInStream = null;
private OutputStream telemetryOutStream = null;
private ParcelFileDescriptor mParcelFileDescriptor;
private FileInputStream mFileInputStream;
private FileOutputStream mFileOutputStream;
private ExecutorService mThreadPool;
private byte[] mBytes = new byte[32 * 1024];
private byte vMaj = 0;
public TaiSync()
{
runLock = new Object();
mThreadPool = Executors.newFixedThreadPool(3);
}
public boolean isRunning()
{
synchronized (runLock) {
return running;
}
}
public void open(ParcelFileDescriptor parcelFileDescriptor) throws IOException
{
// Log.i("QGC_TaiSync", "Open");
synchronized (runLock) {
if (running) {
return;
}
running = true;
}
mParcelFileDescriptor = parcelFileDescriptor;
if (mParcelFileDescriptor == null) {
throw new IOException("parcelFileDescriptor is null");
}
FileDescriptor fileDescriptor = mParcelFileDescriptor.getFileDescriptor();
mFileInputStream = new FileInputStream(fileDescriptor);
mFileOutputStream = new FileOutputStream(fileDescriptor);
udpSocket = new DatagramSocket();
final InetAddress address = InetAddress.getByName("localhost");
tcpTelemetrySocket = new Socket(address, TAISYNC_TELEMETRY_PORT);
telemetryInStream = tcpTelemetrySocket.getInputStream();
telemetryOutStream = tcpTelemetrySocket.getOutputStream();
tcpSettingsSocket = new Socket(address, TAISYNC_SETTINGS_PORT);
settingsInStream = tcpSettingsSocket.getInputStream();
settingsOutStream = tcpSettingsSocket.getOutputStream();
// Request connection packet
sendTaiSyncMessage(PROTOCOL_REQUEST_CONNECTION, 0, null, 0);
// Read multiplexed data stream coming from TaiSync accessory
mThreadPool.execute(new Runnable() {
@Override
public void run()
{
int bytesRead = 0;
try {
while (bytesRead >= 0) {
synchronized (runLock) {
if (!running) {
break;
}
}
bytesRead = mFileInputStream.read(mBytes);
if (bytesRead > 0)
{
if (mBytes[3] == PROTOCOL_VERSION)
{
vMaj = mBytes[19];
Log.i("QGC_TaiSync", "Got protocol version message vMaj = " + mBytes[19]);
sendTaiSyncMessage(PROTOCOL_VERSION, 0, null, 0);
}
else if (mBytes[3] == PROTOCOL_CHANNEL) {
int dPort = ((mBytes[4] & 0xff)<< 24) | ((mBytes[5]&0xff) << 16) | ((mBytes[6]&0xff) << 8) | (mBytes[7] &0xff);
int dLength = ((mBytes[8] & 0xff)<< 24) | ((mBytes[9]&0xff) << 16) | ((mBytes[10]&0xff) << 8) | (mBytes[11] &0xff);
Log.i("QGC_TaiSync", "Read 2 port = " + dPort + " length = " + dLength);
sendTaiSyncMessage(PROTOCOL_CHANNEL, dPort, null, 0);
}
else if (mBytes[3] == PROTOCOL_DATA) {
int dPort = ((mBytes[4] & 0xff)<< 24) | ((mBytes[5]&0xff) << 16) | ((mBytes[6]&0xff) << 8) | (mBytes[7] &0xff);
int dLength = ((mBytes[8] & 0xff)<< 24) | ((mBytes[9]&0xff) << 16) | ((mBytes[10]&0xff) << 8) | (mBytes[11] &0xff);
int payloadOffset = HEADER_SIZE;
int payloadLength = bytesRead - payloadOffset;
byte[] sBytes = new byte[payloadLength];
System.arraycopy(mBytes, payloadOffset, sBytes, 0, payloadLength);
if (dPort == TAISYNC_VIDEO_PORT) {
DatagramPacket packet = new DatagramPacket(sBytes, sBytes.length, address, VIDEO_PORT);
udpSocket.send(packet);
} else if (dPort == TAISYNC_SETTINGS_PORT) {
settingsOutStream.write(sBytes);
} else if (dPort == TAISYNC_TELEMETRY_PORT) {
telemetryOutStream.write(sBytes);
}
}
}
}
} catch (IOException e) {
Log.e("QGC_TaiSync", "Exception: " + e);
e.printStackTrace();
} finally {
close();
}
}
});
// Read command & control packets to be sent to telemetry port
mThreadPool.execute(new Runnable() {
@Override
public void run()
{
byte[] inbuf = new byte[256];
try {
while (true) {
synchronized (runLock) {
if (!running) {
break;
}
}
int bytesRead = telemetryInStream.read(inbuf, 0, inbuf.length);
if (bytesRead > 0) {
sendTaiSyncMessage(PROTOCOL_DATA, TAISYNC_TELEMETRY_PORT, inbuf, bytesRead);
}
}
} catch (IOException e) {
Log.e("QGC_TaiSync", "Exception: " + e);
e.printStackTrace();
} finally {
close();
}
}
});
// Read incoming requests for settings socket
mThreadPool.execute(new Runnable() {
@Override
public void run()
{
byte[] inbuf = new byte[1024];
try {
while (true) {
synchronized (runLock) {
if (!running) {
break;
}
}
int bytesRead = settingsInStream.read(inbuf, 0, inbuf.length);
if (bytesRead > 0) {
sendTaiSyncMessage(PROTOCOL_DATA, TAISYNC_SETTINGS_PORT, inbuf, bytesRead);
}
}
} catch (IOException e) {
Log.e("QGC_TaiSync", "Exception: " + e);
e.printStackTrace();
} finally {
close();
}
}
});
}
private void sendTaiSyncMessage(byte protocol, int dataPort, byte[] data, int dataLen) throws IOException
{
byte portMSB = (byte)((dataPort >> 8) & 0xFF);
byte portLSB = (byte)(dataPort & 0xFF);
byte[] lA = new byte[4];
int len = HEADER_SIZE + dataLen;
Log.i("QGC_TaiSync", "Sending to " + dataPort + " length = " + len);
byte[] buffer = new byte[len];
for (int i = 3; i >= 0; i--) {
lA[i] = (byte)(len & 0xFF);
len >>= 8;
}
byte[] header = { 0x00, 0x00, 0x00, protocol, // uint32 - protocol
0x00, 0x00, portMSB, portLSB, // uint32 - dport
lA[0], lA[1], lA[2], lA[3], // uint32 - length
0x00, 0x00, 0x00, 0x00, // uint32 - magic
0x00, 0x00, 0x00, vMaj, // uint32 - version major
0x00, 0x00, 0x00, 0x00, // uint32 - version minor
0x00, 0x00, 0x00, 0x00 }; // uint32 - padding
System.arraycopy(header, 0, buffer, 0, header.length);
if (data != null && dataLen > 0) {
System.arraycopy(data, 0, buffer, header.length, dataLen);
}
synchronized (runLock) {
mFileOutputStream.write(buffer);
}
}
public void close()
{
// Log.i("QGC_TaiSync", "Close");
synchronized (runLock) {
running = false;
}
try {
if (udpSocket != null) {
udpSocket.close();
}
} catch (Exception e) {
}
try {
if (tcpTelemetrySocket != null) {
tcpTelemetrySocket.close();
}
} catch (Exception e) {
}
try {
if (tcpSettingsSocket != null) {
tcpSettingsSocket.close();
}
} catch (Exception e) {
}
try {
if (mParcelFileDescriptor != null) {
mParcelFileDescriptor.close();
}
} catch (Exception e) {
}
udpSocket = null;
tcpSettingsSocket = null;
tcpTelemetrySocket = null;
settingsInStream = null;
settingsOutStream = null;
mParcelFileDescriptor = null;
}
}
......@@ -70,16 +70,19 @@ The installer places them under ~/Library/Developer/GStreamer/iPhone.sdk/GStream
### Android
Binaries found in http://gstreamer.freedesktop.org/data/pkg/android
Download the [gstreamer-1.0-android-armv7-1.5.2.tar.bz2](http://gstreamer.freedesktop.org/data/pkg/android/1.5.2/gstreamer-1.0-android-armv7-1.5.2.tar.bz2) archive (assuming you want the ARM V7 platform. For x86, replace `armv7` with `x86` accordingly).
Download the [gstreamer-1.0-android-universal-1.14.4.tar.bz2](https://gstreamer.freedesktop.org/data/pkg/android/1.14.4/gstreamer-1.0-android-universal-1.14.4.tar.bz2) archive.
Create a directory named "gstreamer-1.0-android-armv7-1.5.2" under the root qgroundcontrol directory (the same directory qgroundcontrol.pro is located). Extract the gstreamer tar file under this directory. That's where the build system will look for it. Make sure your archive tool doesn't create any additional top level directories. The structure after extracting the archive should look like this:
Create a directory named "gstreamer-1.0-android-universal-1.14.4" under the root qgroundcontrol directory (the same directory qgroundcontrol.pro is located). Extract the downloaded archive under this directory. That's where the build system will look for it. Make sure your archive tool doesn't create any additional top level directories. The structure after extracting the archive should look like this:
```
qgroundcontrol
├── gstreamer-1.0-android-armv7-1.5.2
│   ├── etc
│   ├── include
│   ├── lib
│   └── share
├── gstreamer-1.0-android-universal-1.14.4
│ │
│   ├──armv7
│   │   ├── etc
│   │   ├── include
│   │   ├── lib
│   │   └── share
│   ├──x86
```
### Windows
......
......@@ -539,7 +539,8 @@ VideoReceiver::_shutdownPipeline() {
void
VideoReceiver::_handleError() {
qCDebug(VideoReceiverLog) << "Gstreamer error!";
_shutdownPipeline();
stop();
start();
}
#endif
......@@ -554,7 +555,8 @@ VideoReceiver::_handleEOS() {
_shutdownRecordingBranch();
} else {
qWarning() << "VideoReceiver: Unexpected EOS!";
_shutdownPipeline();
stop();
start();
}
}
#endif
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment