From cf58cb16f0f9e93b8e923615c89c9b732b5c3cd4 Mon Sep 17 00:00:00 2001 From: wangshuo <584363327@qq.com> Date: Sat, 27 May 2023 17:47:51 +0800 Subject: [PATCH 1/9] =?UTF-8?q?android=20sdk=E6=94=AF=E6=8C=81=E8=99=9A?= =?UTF-8?q?=E6=8B=9F=E4=BB=BF=E7=9C=9F=E8=AE=BE=E5=A4=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 9 + .../cloudapp/ui/CasCloudPhoneActivity.java | 66 +++++ .../huawei/cloudphone/ICASAidlInterface.aidl | 2 +- .../huawei/cloudphone/ICASAidlListener.aidl | 2 + cloudphone/src/main/cpp/CasCommon.h | 6 + cloudphone/src/main/cpp/CasController.cpp | 18 ++ cloudphone/src/main/cpp/CasController.h | 1 + cloudphone/src/main/cpp/CasOpusBridge.cpp | 7 +- cloudphone/src/main/cpp/CasOpusBridge.h | 2 +- cloudphone/src/main/cpp/cas_common/CasMsg.h | 7 + .../cpp/cas_stream/CasStreamBuildSender.cpp | 9 + .../cpp/cas_stream/CasStreamRecvParser.cpp | 2 +- .../api/CloudPhoneVirtualDevDataListener.java | 5 + .../huawei/cloudphone/api/ICloudPhone.java | 14 + .../cloudphone/apiimpl/CloudPhoneImpl.java | 25 +- .../cloudphone/jniwrapper/JNIWrapper.java | 5 + .../cloudphone/jniwrapper/OpusJNIWrapper.java | 2 +- .../huawei/cloudphone/service/CASClient.java | 15 +- .../cloudphone/service/CasProcessor.java | 25 +- .../virtualdevice/VirtualDeviceSession.java | 42 +++ .../virtualdevice/camera/AvcEncoder.java | 212 ++++++++++++++ .../camera/AvcEncoderDataHandler.java | 11 + .../virtualdevice/camera/VirtualCamera.java | 275 ++++++++++++++++++ .../camera/VirtualCameraManager.java | 188 ++++++++++++ .../common/IVirtualDeviceDataListener.java | 10 + .../common/IVirtualDeviceIO.java | 23 ++ .../virtualdevice/common/ParamBundle.java | 30 ++ .../virtualdevice/common/RingBuffer.java | 72 +++++ .../common/RingBufferVirtualDeviceIO.java | 63 ++++ .../common/VirtualDeviceManager.java | 18 ++ .../common/VirtualDeviceProtocol.java | 219 ++++++++++++++ .../microphone/VirtualMicrophone.java | 125 ++++++++ .../microphone/VirtualMicrophoneManager.java | 127 ++++++++ .../virtualdevice/sensor/VirtualSensor.java | 79 +++++ .../sensor/VirtualSensorManager.java | 83 ++++++ 35 files changed, 1783 insertions(+), 16 deletions(-) create mode 100644 cloudphone/src/main/java/com/huawei/cloudphone/api/CloudPhoneVirtualDevDataListener.java create mode 100644 cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/VirtualDeviceSession.java create mode 100644 cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/AvcEncoder.java create mode 100644 cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/AvcEncoderDataHandler.java create mode 100644 cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/VirtualCamera.java create mode 100644 cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/VirtualCameraManager.java create mode 100644 cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/IVirtualDeviceDataListener.java create mode 100644 cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/IVirtualDeviceIO.java create mode 100644 cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/ParamBundle.java create mode 100644 cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/RingBuffer.java create mode 100644 cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/RingBufferVirtualDeviceIO.java create mode 100644 cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/VirtualDeviceManager.java create mode 100644 cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/VirtualDeviceProtocol.java create mode 100644 cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/microphone/VirtualMicrophone.java create mode 100644 cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/microphone/VirtualMicrophoneManager.java create mode 100644 cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/sensor/VirtualSensor.java create mode 100644 cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/sensor/VirtualSensorManager.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6ed70cd..839ab18 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,15 @@ package="com.huawei.cloudapp"> + + + + + + 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) & isGotCameraAndRecordPermission; + } + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + + } + /** * get data with intent */ @@ -383,6 +411,28 @@ public class CasCloudPhoneActivity extends FragmentActivity implements View.OnCl } } + private void initVirtualDeviceSession() { + if (!isGotCameraAndRecordPermission) return; + mVirtualDeviceSession = new VirtualDeviceSession(this); + mRingBufferVirtualDeviceIO = new RingBufferVirtualDeviceIO(); + mVirtualDeviceSession.setVirtualDeviceIoHook(mRingBufferVirtualDeviceIO); + } + + private void getPermissions() { + if (Build.VERSION.SDK_INT < LOLLIPOP_MR1) return; + if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE} + , CAMERA_PERMISSION_REQUEST_CODE); + } + if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE} + , RECORD_AUDIO_PERMISSION_REQUEST_CODE); + } + + } + private void processStateChangeMsg(int code) { CASLog.i(TAG, "processStateChangeMsg code = " + code); switch (code) { @@ -390,6 +440,9 @@ public class CasCloudPhoneActivity extends FragmentActivity implements View.OnCl lag.setVisibility(View.VISIBLE); loadingView.setVisibility(View.GONE); startSuccessThreadTask(); + if (mVirtualDeviceSession != null) { + mVirtualDeviceSession.start(); + } break; case CasState.CAS_SERVER_UNREACHABLE: showDialog(getResources().getString(R.string.cas_phone_connect_server_fail_tip_message)); @@ -603,6 +656,9 @@ public class CasCloudPhoneActivity extends FragmentActivity implements View.OnCl bIsStart = false; try { mCloudPhone.exitCloudPhone(); + if (mVirtualDeviceSession != null) { + mVirtualDeviceSession.stop(); + } } catch (Exception e) { CASLog.e(TAG, "stop cloud phone failed " + e.getMessage()); } @@ -781,6 +837,16 @@ public class CasCloudPhoneActivity extends FragmentActivity implements View.OnCl } } + class CloudPhoneVirtualDevDataListenerImpl implements CloudPhoneVirtualDevDataListener { + + @Override + public void onRecvVirtualDevData(byte[] data, int length) { + if (mRingBufferVirtualDeviceIO != null) { + mRingBufferVirtualDeviceIO.fillData(data); + } + } + } + public void switchCtrlViewShow() { if (ctrView == null) { return; diff --git a/cloudphone/src/main/aidl/com/huawei/cloudphone/ICASAidlInterface.aidl b/cloudphone/src/main/aidl/com/huawei/cloudphone/ICASAidlInterface.aidl index bafce7e..561f92e 100644 --- a/cloudphone/src/main/aidl/com/huawei/cloudphone/ICASAidlInterface.aidl +++ b/cloudphone/src/main/aidl/com/huawei/cloudphone/ICASAidlInterface.aidl @@ -72,5 +72,5 @@ interface ICASAidlInterface { void mute(in boolean isMute); - void sendDataToCloudApp(in byte[] data); + void sendData(byte type, in byte[] data); } diff --git a/cloudphone/src/main/aidl/com/huawei/cloudphone/ICASAidlListener.aidl b/cloudphone/src/main/aidl/com/huawei/cloudphone/ICASAidlListener.aidl index f4e146b..c3a9fee 100644 --- a/cloudphone/src/main/aidl/com/huawei/cloudphone/ICASAidlListener.aidl +++ b/cloudphone/src/main/aidl/com/huawei/cloudphone/ICASAidlListener.aidl @@ -26,4 +26,6 @@ interface ICASAidlListener { void onCmdRecv(int code, String msg); void onChannelDataRecv(in byte[] data); + + void onVirtualDevDataRecv(in byte[] data); } diff --git a/cloudphone/src/main/cpp/CasCommon.h b/cloudphone/src/main/cpp/CasCommon.h index 6b958c7..01d70e1 100644 --- a/cloudphone/src/main/cpp/CasCommon.h +++ b/cloudphone/src/main/cpp/CasCommon.h @@ -39,6 +39,12 @@ const std::string KEY_BITRATE = "bitrate"; const std::string KEY_VIRTUAL_WIDTH = "virtual_width"; const std::string KEY_VIRTUAL_HEIGHT = "virtual_height"; +enum VirtualDevice { + VIRTUAL_CAMERA, + VIRTUAL_MICROPHONE, + VIRTUAL_SENSOR +}; + enum { CAS_CONNECTING = 0x0100, CAS_CONNECT_SUCCESS = 0x0200, diff --git a/cloudphone/src/main/cpp/CasController.cpp b/cloudphone/src/main/cpp/CasController.cpp index 08ebabf..0b46d59 100644 --- a/cloudphone/src/main/cpp/CasController.cpp +++ b/cloudphone/src/main/cpp/CasController.cpp @@ -65,6 +65,7 @@ CasController::CasController() m_videoPacketStream = nullptr; m_audioPacketStream = nullptr; m_orientationStream = nullptr; + m_virtualDeviceStream = nullptr; m_controlStream = nullptr; m_channelStream = nullptr; m_cmdController = nullptr; @@ -85,6 +86,7 @@ CasController::~CasController() m_videoPacketStream = nullptr; m_audioPacketStream = nullptr; m_orientationStream = nullptr; + m_virtualDeviceStream = nullptr; m_controlStream = nullptr; m_channelStream = nullptr; m_cmdController = nullptr; @@ -398,6 +400,7 @@ bool CasController::CreateWorkers(ANativeWindow *nativeWindow, bool needVideoDec m_streamParser->SetServiceHandle(CasMsgType::Video, m_videoPacketStream); m_streamParser->SetServiceHandle(CasMsgType::Audio, m_audioPacketStream); m_streamParser->SetServiceHandle(CasMsgType::Channel, m_channelStream); + m_streamParser->SetServiceHandle(CasMsgType::VirtualDevice, m_virtualDeviceStream); if (needVideoDecode) { m_videoDecodeThread = new (std::nothrow) CasVideoHDecodeThread(nativeWindow); @@ -593,6 +596,12 @@ bool CasController::InitDataStream() return false; } + m_virtualDeviceStream = new (std::nothrow) CasDataPipe(); + if (m_virtualDeviceStream == nullptr) { + ERR("Failed to new virtual device packet stream."); + return false; + } + return true; } @@ -618,6 +627,10 @@ bool CasController::ClearDataStream() m_channelStream->Clear(); } + if (m_virtualDeviceStream != nullptr) { + m_virtualDeviceStream->Clear(); + } + INFO("Succeed to clear data stream "); return true; } @@ -745,6 +758,11 @@ int CasController::JniRecvData(CasMsgType type, uint8_t *data, int length) pPkt = m_channelStream->GetNextPkt(); } break; + case CasMsgType::VirtualDevice: + if (m_virtualDeviceStream != nullptr) { + pPkt = m_virtualDeviceStream->GetNextPkt(); + } + break; default: ERR("Invalid type %d, length %d.", type, length); return 0; diff --git a/cloudphone/src/main/cpp/CasController.h b/cloudphone/src/main/cpp/CasController.h index 2ef53ca..f7d61db 100644 --- a/cloudphone/src/main/cpp/CasController.h +++ b/cloudphone/src/main/cpp/CasController.h @@ -125,6 +125,7 @@ private: CasDataPipe *m_orientationStream = nullptr; CasDataPipe *m_controlStream = nullptr; CasDataPipe *m_channelStream = nullptr; + CasDataPipe *m_virtualDeviceStream = nullptr; CasCmdController *m_cmdController = nullptr; CasHeartbeatController *m_heartbeatController = nullptr; diff --git a/cloudphone/src/main/cpp/CasOpusBridge.cpp b/cloudphone/src/main/cpp/CasOpusBridge.cpp index 001f56e..1f6f840 100644 --- a/cloudphone/src/main/cpp/CasOpusBridge.cpp +++ b/cloudphone/src/main/cpp/CasOpusBridge.cpp @@ -97,7 +97,7 @@ extern "C" JNIEXPORT jint JNICALL Java_com_huawei_cloudphone_jniwrapper_OpusJNIW } extern "C" JNIEXPORT jint JNICALL Java_com_huawei_cloudphone_jniwrapper_OpusJNIWrapper_createOpusEncoder(JNIEnv *env, - jclass type, jint sampleRate, jint channels, jint bitrate) + jclass type, jint sampleRate, jint channels) { int err = 0; g_encoder = opus_encoder_create(sampleRate, channels, APPLICATION, &err); @@ -106,8 +106,9 @@ extern "C" JNIEXPORT jint JNICALL Java_com_huawei_cloudphone_jniwrapper_OpusJNIW g_encoder = nullptr; return JNI_ERR; } - - err = opus_encoder_ctl(g_encoder, OPUS_SET_BITRATE(bitrate)); + err += opus_encoder_ctl(g_encoder, OPUS_SET_BANDWIDTH(OPUS_BANDWIDTH_WIDEBAND)); + err += opus_encoder_ctl(g_encoder, OPUS_SET_VBR(1)); + err += opus_encoder_ctl(g_encoder, OPUS_SET_COMPLEXITY(10)); if (err < 0) { ERR("Failed to set bitrate %s", opus_strerror(err)); g_encoder = nullptr; diff --git a/cloudphone/src/main/cpp/CasOpusBridge.h b/cloudphone/src/main/cpp/CasOpusBridge.h index db67993..94f1dca 100644 --- a/cloudphone/src/main/cpp/CasOpusBridge.h +++ b/cloudphone/src/main/cpp/CasOpusBridge.h @@ -58,7 +58,7 @@ JNIEXPORT jint JNICALL Java_com_huawei_cloudphone_jniwrapper_OpusJNIWrapper_opus * Signature: (III)I */ JNIEXPORT jint JNICALL Java_com_huawei_cloudphone_jniwrapper_OpusJNIWrapper_createOpusEncoder(JNIEnv *, jclass type, - jint, jint, jint); + jint, jint); /* * Class: com_huawei_cloudphone_jniwrapper_OpusJNIWrapper diff --git a/cloudphone/src/main/cpp/cas_common/CasMsg.h b/cloudphone/src/main/cpp/cas_common/CasMsg.h index 8a44a05..6414caf 100644 --- a/cloudphone/src/main/cpp/cas_common/CasMsg.h +++ b/cloudphone/src/main/cpp/cas_common/CasMsg.h @@ -33,6 +33,10 @@ enum CasMsgType : uint8_t { Recorder = 11, KeyEventInput = 15, MotionEventInput = 16, + VirtualDevice = 20, + VirtualCamera = 21, + VirtualMicrophone = 22, + VirtualSensor = 23, End, }; @@ -52,6 +56,9 @@ enum CasMsgType : uint8_t { #define CAS_MSG_CHECKSUM_KEYEVENTINPUT GET_CAS_CHECKSUM(CasMsgType::KeyEventInput) #define CAS_MSG_CHECKSUM_MOTIONEVENTINPUT GET_CAS_CHECKSUM(CasMsgType::MotionEventInput) #define CAS_MSG_CHECKSUM_CHANNEL GET_CAS_CHECKSUM(CasMsgType::Channel) +#define CAS_MSG_CHECKSUM_VIRTUAL_CAMERA GET_CAS_CHECKSUM(CasMsgType::VirtualCamera) +#define CAS_MSG_CHECKSUM_VIRTUAL_MICROPHONE GET_CAS_CHECKSUM(CasMsgType::VirtualMicrophone) +#define CAS_MSG_CHECKSUM_VIRTUAL_SENSOR GET_CAS_CHECKSUM(CasMsgType::VirtualSensor) // 客户端通用消息头 typedef struct streamMsgHead { diff --git a/cloudphone/src/main/cpp/cas_stream/CasStreamBuildSender.cpp b/cloudphone/src/main/cpp/cas_stream/CasStreamBuildSender.cpp index 78b9cc2..1257654 100644 --- a/cloudphone/src/main/cpp/cas_stream/CasStreamBuildSender.cpp +++ b/cloudphone/src/main/cpp/cas_stream/CasStreamBuildSender.cpp @@ -72,6 +72,15 @@ int CasStreamBuildSender::SendDataToServer(CasMsgType type, const void *buf, siz case (Channel): msgHead.checksum = CAS_MSG_CHECKSUM_CHANNEL; break; + case (VirtualCamera): + msgHead.checksum = CAS_MSG_CHECKSUM_VIRTUAL_CAMERA; + break; + case (VirtualMicrophone): + msgHead.checksum = CAS_MSG_CHECKSUM_VIRTUAL_MICROPHONE; + break; + case (VirtualSensor): + msgHead.checksum = CAS_MSG_CHECKSUM_VIRTUAL_SENSOR; + break; default: { return -1; } diff --git a/cloudphone/src/main/cpp/cas_stream/CasStreamRecvParser.cpp b/cloudphone/src/main/cpp/cas_stream/CasStreamRecvParser.cpp index 6197e38..60c1257 100644 --- a/cloudphone/src/main/cpp/cas_stream/CasStreamRecvParser.cpp +++ b/cloudphone/src/main/cpp/cas_stream/CasStreamRecvParser.cpp @@ -75,7 +75,7 @@ void CasStreamRecvParser::SetServiceHandle(unsigned char type, CasPktHandle *ser CasPktHandle *CasStreamRecvParser::GetServiceHandle(unsigned char type) { - return m_serviceHandles[type]; + return VirtualSensor >= type && type >= VirtualCamera ? m_serviceHandles[VirtualDevice] : m_serviceHandles[type]; } CasStreamParseThread::CasStreamParseThread(CasSocket *socket, CasStreamRecvParser *streamRecvParser) diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/api/CloudPhoneVirtualDevDataListener.java b/cloudphone/src/main/java/com/huawei/cloudphone/api/CloudPhoneVirtualDevDataListener.java new file mode 100644 index 0000000..87a2119 --- /dev/null +++ b/cloudphone/src/main/java/com/huawei/cloudphone/api/CloudPhoneVirtualDevDataListener.java @@ -0,0 +1,5 @@ +package com.huawei.cloudphone.api; + +public interface CloudPhoneVirtualDevDataListener { + void onRecvVirtualDevData(byte[] data, int length); +} diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/api/ICloudPhone.java b/cloudphone/src/main/java/com/huawei/cloudphone/api/ICloudPhone.java index 0311b01..5cf371a 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/api/ICloudPhone.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/api/ICloudPhone.java @@ -115,6 +115,20 @@ public interface ICloudPhone { */ void registerOnOrientationChangeListener(CloudPhoneOrientationChangeListener listener); + /** + * 设置虚拟设备的回调 + * + * @param listener + */ + void registerOnVirtualDevDataListener(CloudPhoneVirtualDevDataListener listener); + + /** + * 发送虚拟设备数据 + * @param devType + * @param data + */ + void sendVirtualDeviceData(byte devType, byte[] data); + /** * 获取网络时延 * diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/apiimpl/CloudPhoneImpl.java b/cloudphone/src/main/java/com/huawei/cloudphone/apiimpl/CloudPhoneImpl.java index 1d0bf5b..ca6d21c 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/apiimpl/CloudPhoneImpl.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/apiimpl/CloudPhoneImpl.java @@ -47,6 +47,7 @@ import com.huawei.cloudphone.api.CloudPhoneOrientationChangeListener; import com.huawei.cloudphone.api.CloudPhoneParas; import com.huawei.cloudphone.api.CloudPhoneParas.DisplayMode; import com.huawei.cloudphone.api.CloudPhoneStateListener; +import com.huawei.cloudphone.api.CloudPhoneVirtualDevDataListener; import com.huawei.cloudphone.api.ICloudPhone; import com.huawei.cloudphone.common.CASLog; import com.huawei.cloudphone.common.CasConnectorInfo; @@ -113,6 +114,7 @@ public class CloudPhoneImpl implements ICloudPhone { private volatile boolean mIsReconnectTaskRun = false; private CloudPhoneStateListener mStateListener = null; private CloudPhoneOrientationChangeListener mOrientationChangeListener = null; + private CloudPhoneVirtualDevDataListener mVirtualDevDataListener = null; private CloudAppDataListener mChannelDataListener = null; private ServiceConnection mConnection = null; private CASListener mListener = null; @@ -295,6 +297,18 @@ public class CloudPhoneImpl implements ICloudPhone { mOrientationChangeListener = listener; } + @Override + public void registerOnVirtualDevDataListener(CloudPhoneVirtualDevDataListener listener) { + mVirtualDevDataListener = listener; + } + + @Override + public void sendVirtualDeviceData(byte devType, byte[] data) { + if (mCASClient != null) { + mCASClient.sendDataToVirtualDevice(devType, data); + } + } + @Override public int getRtt() { if (mCASClient != null) { @@ -495,7 +509,7 @@ public class CloudPhoneImpl implements ICloudPhone { mCASClient.pause(); mCurrentState = STATE_PAUSE; mBackGroundTimer = new Timer(); - mBackGroundTimer.schedule(new BackgroudTimerTask(), mBgTimeout); + mBackGroundTimer.schedule(new BackgroundTimerTask(), mBgTimeout); } } } @@ -767,9 +781,16 @@ public class CloudPhoneImpl implements ICloudPhone { mChannelDataListener.onRecvCloudAppData(data); } } + + @Override + public void onVirtualDevDataRecv(byte[] data) throws RemoteException { + if (mVirtualDevDataListener != null) { + mVirtualDevDataListener.onRecvVirtualDevData(data, data.length); + } + } } - private class BackgroudTimerTask extends TimerTask { + private class BackgroundTimerTask extends TimerTask { @Override public void run() { CASLog.i(TAG, "BackgroudTimerTask run"); diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/jniwrapper/JNIWrapper.java b/cloudphone/src/main/java/com/huawei/cloudphone/jniwrapper/JNIWrapper.java index 95421af..f9d6265 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/jniwrapper/JNIWrapper.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/jniwrapper/JNIWrapper.java @@ -51,6 +51,11 @@ public class JNIWrapper { public static final byte NOTIFY = 17; public static final byte END = 19; + public static final byte VIRTUAL_DEVICE_DATA = 20; + public static final byte CAMERA_DATA = 21; + public static final byte MICROPHONE_DATA = 22; + public static final byte SENSOR_DATA = 23; + static { System.loadLibrary("cloudapp"); } diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/jniwrapper/OpusJNIWrapper.java b/cloudphone/src/main/java/com/huawei/cloudphone/jniwrapper/OpusJNIWrapper.java index 2871dbb..a5dd934 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/jniwrapper/OpusJNIWrapper.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/jniwrapper/OpusJNIWrapper.java @@ -29,7 +29,7 @@ public class OpusJNIWrapper { public static native int opusDecode(long decoder, byte[] inputBuffer, int inputBufferLen, short[] outBuffer, int outbufLen); - public static native int createOpusEncoder(int sampleRate, int channels, int bitrate); + public static native int createOpusEncoder(int sampleRate, int channels); public static native int destroyOpusEncoder(); diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/service/CASClient.java b/cloudphone/src/main/java/com/huawei/cloudphone/service/CASClient.java index 1f46aab..274b22c 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/service/CASClient.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/service/CASClient.java @@ -16,6 +16,8 @@ package com.huawei.cloudphone.service; +import static com.huawei.cloudphone.jniwrapper.JNIWrapper.CHANNEL; + import android.os.IBinder; import android.os.RemoteException; import android.view.Surface; @@ -321,9 +323,20 @@ public class CASClient { return; } try { - mCasInterface.sendDataToCloudApp(data); + mCasInterface.sendData(CHANNEL, data); } catch (RemoteException e) { CASLog.e(TAG, "failed to send data to cloud app."); } } + + public void sendDataToVirtualDevice(byte devType, byte[] data) { + if (mCasInterface == null) { + return; + } + try { + mCasInterface.sendData(devType, data); + } catch (RemoteException e) { + CASLog.e(TAG, "failed to send data to virtual device."); + } + } } diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/service/CasProcessor.java b/cloudphone/src/main/java/com/huawei/cloudphone/service/CasProcessor.java index 0d0eb0a..cf16822 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/service/CasProcessor.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/service/CasProcessor.java @@ -40,15 +40,11 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.HashMap; -import static com.huawei.cloudphone.jniwrapper.JNIWrapper.AUDIO; -import static com.huawei.cloudphone.jniwrapper.JNIWrapper.CHANNEL; import static com.huawei.cloudphone.jniwrapper.JNIWrapper.KEY_AES_IV; import static com.huawei.cloudphone.jniwrapper.JNIWrapper.KEY_AUTH_TS; -import static com.huawei.cloudphone.jniwrapper.JNIWrapper.KEY_DECODE_METHOD; import static com.huawei.cloudphone.jniwrapper.JNIWrapper.KEY_ENCRYPTED_DATA; import static com.huawei.cloudphone.jniwrapper.JNIWrapper.KEY_BACKGROUND_TIMEOUT; import static com.huawei.cloudphone.jniwrapper.JNIWrapper.KEY_IP; -import static com.huawei.cloudphone.jniwrapper.JNIWrapper.KEY_LOG_LEVEL; import static com.huawei.cloudphone.jniwrapper.JNIWrapper.KEY_PORT; import static com.huawei.cloudphone.jniwrapper.JNIWrapper.KEY_PROTOCOL_VERSION; import static com.huawei.cloudphone.jniwrapper.JNIWrapper.KEY_SDK_VERSION; @@ -91,6 +87,7 @@ public class CasProcessor extends ICASAidlInterface.Stub { */ private ICASAidlListener mListener = null; private NewRotationDirectionPacket mNewRotationDirPkt; + private NewVirtualDevDataPacket mVirtualDevDataPkt; @Override public void init() throws RemoteException { @@ -218,10 +215,12 @@ public class CasProcessor extends ICASAidlInterface.Stub { mAudioTrackerCallback = new AudioTrackerCallback(); mNewRotationDirPkt = new NewRotationDirectionPacket(); mChannelDataCallback = new NewChannelDataPacket(); + mVirtualDevDataPkt = new NewVirtualDevDataPacket(); mUpstreamReceiveDispatcher.addNewPacketCallback((byte) JNIWrapper.ORIENTATION, mNewRotationDirPkt); mUpstreamReceiveDispatcher.addNewPacketCallback((byte) JNIWrapper.AUDIO, mAudioTrackerCallback); mUpstreamReceiveDispatcher.addNewPacketCallback((byte) JNIWrapper.CHANNEL, mChannelDataCallback); + mUpstreamReceiveDispatcher.addNewPacketCallback((byte) JNIWrapper.VIRTUAL_DEVICE_DATA, mVirtualDevDataPkt); mUpstreamReceiveDispatcher.start(); return true; } @@ -232,6 +231,7 @@ public class CasProcessor extends ICASAidlInterface.Stub { mUpstreamReceiveDispatcher.deleteNewPacketCallback((byte) JNIWrapper.AUDIO); mUpstreamReceiveDispatcher.deleteNewPacketCallback((byte) JNIWrapper.ORIENTATION); mUpstreamReceiveDispatcher.deleteNewPacketCallback((byte) JNIWrapper.CHANNEL); + mUpstreamReceiveDispatcher.deleteNewPacketCallback((byte) JNIWrapper.VIRTUAL_DEVICE_DATA); mUpstreamReceiveDispatcher.stopBlocked(); mAudioTrackerCallback.closeAudioTrack(); mUpstreamReceiveDispatcher = null; @@ -268,8 +268,8 @@ public class CasProcessor extends ICASAidlInterface.Stub { } @Override - public void sendDataToCloudApp(byte[] data) throws RemoteException { - JniBridge.getInstance().sendData(CHANNEL, data, data.length); + public void sendData(byte devType, byte[] data) throws RemoteException { + JniBridge.getInstance().sendData(devType, data, data.length); } /** @@ -340,4 +340,17 @@ public class CasProcessor extends ICASAidlInterface.Stub { } } } + + private class NewVirtualDevDataPacket implements NewPacketCallback { + @Override + public void onNewPacket(byte[] data) { + if (mListener != null) { + try { + mListener.onVirtualDevDataRecv(data); + } catch (RemoteException e) { + CASLog.e(TAG, "call onVirtualDevDataRecv failed." + e.getMessage()); + } + } + } + } } diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/VirtualDeviceSession.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/VirtualDeviceSession.java new file mode 100644 index 0000000..8501357 --- /dev/null +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/VirtualDeviceSession.java @@ -0,0 +1,42 @@ +package com.huawei.cloudphone.virtualdevice; + +import android.content.Context; +import android.hardware.SensorManager; +import android.view.SurfaceHolder; + +import com.huawei.cloudphone.virtualdevice.common.IVirtualDeviceIO; +import com.huawei.cloudphone.virtualdevice.common.ParamBundle; +import com.huawei.cloudphone.virtualdevice.common.VirtualDeviceProtocol; + +public class VirtualDeviceSession { + VirtualDeviceProtocol mVirtualDevice; + Context mContext; + ParamBundle mParamBundle; + IVirtualDeviceIO mVirtualDeviceIO; + + public VirtualDeviceSession(Context context) { + mParamBundle = new ParamBundle(); + mParamBundle.setAppContext(context); + mContext = context; + } + + public void setCameraSurfaceHolder(SurfaceHolder surfaceHolder) { + mParamBundle.setSurfaceHolder(surfaceHolder); + } + + public void setVirtualDeviceIoHook(IVirtualDeviceIO virtualDeviceIO) { + mVirtualDeviceIO = virtualDeviceIO; + } + + public void start() { + mVirtualDevice = new VirtualDeviceProtocol(mVirtualDeviceIO, (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE)); + mVirtualDevice.startProcess(); + } + + public void stop() { + if (mVirtualDevice != null) { + mVirtualDevice.stopProcess(); + mVirtualDevice = null; + } + } +} diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/AvcEncoder.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/AvcEncoder.java new file mode 100644 index 0000000..b036237 --- /dev/null +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/AvcEncoder.java @@ -0,0 +1,212 @@ +package com.huawei.cloudphone.virtualdevice.camera; + +import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar; +import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar; + +import android.graphics.ImageFormat; +import android.media.MediaCodec; +import android.media.MediaCodecInfo; +import android.media.MediaCodecList; +import android.media.MediaFormat; +import android.util.Log; + +import java.io.IOException; +import java.nio.ByteBuffer; + +public class AvcEncoder { + private static final String TAG = "AvcEncoder"; + private static final String MIME_TYPE = "video/avc"; + private static final int TIMEOUT_USC = 1000; + private static final int MICROSECONDS_PER_SECOND = 1000000; + private static final int I_FRAME_INTERVAL = 15; + + private static int colorFormat = COLOR_FormatYUV420Planar; + private MediaCodec mediaCodec; + private int mWidth; + private int mHeight; + private int mFrameRate; + private long genIndex; + private byte[] configByte; + private AvcEncoderDataHandler dataHandler; + + public void getIFrame() { + mediaCodec.flush(); + } + + public AvcEncoder(int width, int height, int frameRate, int bitrate, AvcEncoderDataHandler dataHandler) { + Log.i(TAG, "Create AvcEncoder, width = " + width + ", height = " + height + + ", frameRate = " + frameRate + ", bitrate = " + bitrate); + this.dataHandler = dataHandler; + this.mWidth = width; + this.mHeight = height; + this.mFrameRate = frameRate; + this.genIndex = 0; + MediaFormat mediaFormat = MediaFormat.createVideoFormat(MIME_TYPE, width, height); + mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat); + mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate); + mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate); + mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, I_FRAME_INTERVAL); + mediaFormat.setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR); + + try { + mediaCodec = MediaCodec.createEncoderByType(MIME_TYPE); + } catch (IOException e) { + Log.e(TAG, "AvcEncoder create encoder by type failed. ", e); + } + mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); + mediaCodec.start(); + } + + public void stopEncoder() { + Log.i(TAG, "Stop encoder."); + try { + mediaCodec.stop(); + mediaCodec.release(); + } catch (IllegalStateException e) { + Log.e(TAG, "AvcEncoder stop Encoder failed. ", e); + } + } + + public void offerEncoder(byte[] frame, byte[] outBuff) { + if ((frame == null) || (outBuff == null)) { + Log.e(TAG, "Offer Encoder input param invalid. "); + return; + } + inputBuffer(frame); + int outBufLen = 0; + while (true) { + MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); + int outBufId = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USC); + Log.i(TAG, "bufferInfo: size = " + bufferInfo.size + ", offset = " + bufferInfo.offset + + ", flags = " + bufferInfo.flags + ", presentationTimeUs = " + bufferInfo.presentationTimeUs); + if (outBufId >= 0) { + ByteBuffer outBuf = mediaCodec.getOutputBuffer(outBufId); + byte[] h264Buf = new byte[bufferInfo.size]; + if (bufferInfo.flags == MediaCodec.BUFFER_FLAG_CODEC_CONFIG) { + configByte = new byte[bufferInfo.size]; + outBuf.get(configByte); + } else if (bufferInfo.flags == MediaCodec.BUFFER_FLAG_KEY_FRAME) { + outBuf.get(h264Buf); + System.arraycopy(configByte, 0, outBuff, 0, configByte.length); + System.arraycopy(h264Buf, 0, outBuff, configByte.length, h264Buf.length); + outBufLen = bufferInfo.size + configByte.length; + dataHandler.handleData(outBuff, outBufLen); + } else { + outBuf.get(h264Buf); + System.arraycopy(h264Buf, 0, outBuf, 0, h264Buf.length); + outBufLen = bufferInfo.size; + dataHandler.handleData(outBuff, outBufLen); + } + mediaCodec.releaseOutputBuffer(outBufId, false); + } else if (outBufId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { + Log.e(TAG, "Output format changed : " + outBufId); + return; + } else if (outBufId == MediaCodec.INFO_TRY_AGAIN_LATER) { + Log.e(TAG, "Codec waiting : " + outBufId); + return; + } else { + Log.e(TAG, "Unknown outBufId : " + outBufId); + return; + } + } + } + + private void inputBuffer(byte[] frame) { + long pts = computePresentationTime(genIndex); + genIndex++; + byte[] yuv420sp = new byte[mWidth * mHeight * 3 / 2]; + if (colorFormat == COLOR_FormatYUV420Planar) { + swapYV12ToI420(frame, yuv420sp, mWidth, mHeight); + }else if (colorFormat == COLOR_FormatYUV420SemiPlanar) { + swapNV21ToNV12(frame, yuv420sp, mWidth, mHeight); + } + int inBuffId = mediaCodec.dequeueInputBuffer(TIMEOUT_USC); + if (inBuffId < 0) { + Log.e(TAG, "Dequeue input buffer error, inBuffId = " + inBuffId); + return; + } + ByteBuffer inBuff = mediaCodec.getInputBuffer(inBuffId); + inBuff.clear(); + inBuff.put(yuv420sp); + mediaCodec.queueInputBuffer(inBuffId, 0, yuv420sp.length, pts, 0); + } + + private void swapYV12ToI420(byte[] yv12Data, byte[] i420Data, int width, int height) { + if (yv12Data == null || i420Data == null) { + return; + } + int frameSize = width * height; + System.arraycopy(yv12Data, 0, i420Data, 0, frameSize); + System.arraycopy(yv12Data, frameSize + frameSize / 4, i420Data, frameSize ,frameSize / 4); + System.arraycopy(yv12Data, frameSize, i420Data, frameSize + frameSize / 4, frameSize / 4); + } + + private void swapNV21ToNV12(byte[] nv21Data, byte[] nv12Data, int width, int height) { + if (nv21Data == null || nv12Data == null) { + return; + } + int frameSize = width * height; + System.arraycopy(nv21Data, 0, nv12Data, 0, frameSize); + for (int i = 0; i < frameSize / 2; i += 2) { + nv12Data[frameSize + i - 1] = nv21Data[i + frameSize]; + } + for (int i = 0; i < frameSize / 2; i += 2) { + nv12Data[frameSize + i] = nv21Data[i + frameSize - 1]; + } + } + + private long computePresentationTime(long frameIndex) { + return frameIndex * MICROSECONDS_PER_SECOND / mFrameRate; + } + + public static int getImageFormat() { + return colorFormat == COLOR_FormatYUV420Planar ? ImageFormat.YV12 : ImageFormat.NV21; + } + + public static boolean isSupportH264() { + MediaCodecInfo codecInfo = selectCodec(MIME_TYPE); + if (codecInfo == null) { + Log.e(TAG, "Couldn't find a codec for " + MIME_TYPE); + return false; + } + return selectColorFormat(codecInfo, MIME_TYPE); + } + + private static MediaCodecInfo selectCodec(String mimeType) { + for (int i = 0; i < MediaCodecList.getCodecCount(); i++) { + MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i); + if(!codecInfo.isEncoder()) { + continue; + } + String[] types = codecInfo.getSupportedTypes(); + for (int j = 0; j < types.length; j++) { + if (types[j].equalsIgnoreCase(mimeType)) { + return codecInfo; + } + } + } + return null; + } + + private static boolean selectColorFormat(MediaCodecInfo codecInfo, String mimeType) { + MediaCodecInfo.CodecCapabilities capabilities; + try { + capabilities = codecInfo.getCapabilitiesForType(mimeType); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Couldn't get capabilities for " + MIME_TYPE); + return false; + } + for (int i = 0; i < capabilities.colorFormats.length; i++) { + int colorFormat = capabilities.colorFormats[i]; + if (colorFormat == COLOR_FormatYUV420SemiPlanar) { + AvcEncoder.colorFormat = COLOR_FormatYUV420SemiPlanar; + return true; + } else if (colorFormat == COLOR_FormatYUV420Planar) { + AvcEncoder.colorFormat = COLOR_FormatYUV420Planar; + return true; + } + } + Log.e(TAG, "Couldn't get a color format for " + codecInfo.getName() + "/" + mimeType); + return false; + } +} diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/AvcEncoderDataHandler.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/AvcEncoderDataHandler.java new file mode 100644 index 0000000..3cb01b8 --- /dev/null +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/AvcEncoderDataHandler.java @@ -0,0 +1,11 @@ +package com.huawei.cloudphone.virtualdevice.camera; + +public interface AvcEncoderDataHandler { + + /** + * 处理编码数据 + * @param data + * @param length + */ + void handleData(byte[] data, int length); +} diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/VirtualCamera.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/VirtualCamera.java new file mode 100644 index 0000000..bef5f2b --- /dev/null +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/VirtualCamera.java @@ -0,0 +1,275 @@ +package com.huawei.cloudphone.virtualdevice.camera; + +import static android.hardware.Camera.Parameters.FOCUS_MODE_AUTO; +import static android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE; +import static android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO; +import static android.hardware.Camera.Parameters.FOCUS_MODE_FIXED; +import static android.hardware.Camera.Parameters.FOCUS_MODE_INFINITY; +import static android.hardware.Camera.Parameters.FOCUS_MODE_MACRO; + +import android.graphics.ImageFormat; +import android.graphics.Rect; +import android.graphics.SurfaceTexture; +import android.graphics.YuvImage; +import android.hardware.Camera; +import android.opengl.GLES11Ext; +import android.util.Log; +import android.view.SurfaceHolder; + +import com.huawei.cloudphone.virtualdevice.common.IVirtualDeviceDataListener; +import com.huawei.cloudphone.virtualdevice.common.ParamBundle; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +public class VirtualCamera implements Camera.PreviewCallback { + + private static final String TAG = "VirtualCamera"; + private Camera.Parameters mParameters = null; + private int mCameraId = 0; + private Camera mCamera = null; + private static final int LOG_LENGTH_LIMIT = 2000; + private static final int SIZE_WIDTH_LIMIT = 1280; + private static final int JPEG_BUFFER_SIZE = 8 * 1024 * 1024; + private static final int DEFAULT_WIDTH = 640; + private static final int DEFAULT_HEIGHT = 480; + + private int mWidth = DEFAULT_WIDTH; + private int mHeight = DEFAULT_HEIGHT; + private int mFps = 30; + private int mBitrate = 4000000; + private SurfaceHolder mSurfaceHolder; + private boolean mIsUseH264; + + private ByteArrayOutputStream jpegBuffer = new ByteArrayOutputStream(JPEG_BUFFER_SIZE); + private Camera.CameraInfo cameraInfo = new Camera.CameraInfo(); + private IVirtualDeviceDataListener mCameraListener = null; + private byte[] mPreviewBuffer = null; + private SurfaceTexture mSurfaceTexture = null; + private byte[] mH264Buffer; + private AvcEncoder mAvcCodec = null; + + public int open(int cameraId) { + mPreviewBuffer = null; + mSurfaceTexture = null; + mCameraId = cameraId; + Camera.getCameraInfo(mCameraId, cameraInfo); + try { + if (mCamera != null) { + mCamera.stopPreview(); + mCamera.setPreviewCallback(null); + mCamera.release(); + mCamera = null; + } + Camera camera = Camera.open(mCameraId); + Camera.Parameters param = camera.getParameters(); + mParameters = setDefaultParameters(param); + camera.release(); + mIsUseH264 = AvcEncoder.isSupportH264(); + } catch (Exception e) { + Log.e(TAG, "open camera failed, ", e); + return -1; + } + return 0; + } + + public int startPreview() { + mCamera = Camera.open(mCameraId); + Camera.Parameters param = mCamera.getParameters(); + if (mIsUseH264) { + param.setPreviewFormat(AvcEncoder.getImageFormat()); + } else { + param.setPreviewFormat(ImageFormat.NV21); + } + + Camera.Size size = param.getPreviewSize(); + if (mWidth <= 0 || mHeight <= 0 || mFps <= 0) { + param.setPreviewSize(size.width, size.height); + } else { + param.setPreviewSize(mWidth, mHeight); + } + + mCamera.setParameters(param); + List mFpsRangeList = new LinkedList(); + mFpsRangeList = param.getSupportedPreviewFpsRange(); + for (int[] ele : mFpsRangeList) { + Log.i(TAG, "startPreview: support fps range is " + ele[0] + "-" + ele[1]); + if ((mFps * 1000 >= ele[0]) && (mFps * 1000 >= ele[1])) { + param.setPreviewFpsRange(ele[0], ele[1]); + } + } + + try { + mSurfaceHolder = ParamBundle.getSurfaceHolder(); + if (mSurfaceHolder != null) { + mCamera.setPreviewCallback(this); + mCamera.setPreviewDisplay(mSurfaceHolder); + mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); + } else { + mSurfaceTexture = new SurfaceTexture(GLES11Ext.GL_TEXTURE_BINDING_EXTERNAL_OES); + mCamera.setPreviewTexture(mSurfaceTexture); + } + } catch (IOException e) { + Log.e(TAG, "startPreview: failed to set preview params", e); + return -1; + } + + int buffSize = mWidth * mHeight * ImageFormat.getBitsPerPixel(ImageFormat.NV21) / 8; + mPreviewBuffer = new byte[buffSize]; + mCamera.addCallbackBuffer(mPreviewBuffer); + mCamera.setPreviewCallbackWithBuffer(this); + + if (mIsUseH264) { + mAvcCodec = new AvcEncoder(mWidth, mHeight, param.getPreviewFrameRate(), mBitrate + , new H264DataHandler()); + mH264Buffer = new byte[mWidth * mHeight * 3 / 2]; + mAvcCodec.getIFrame(); + } + mCamera.startPreview(); + return 0; + } + + public void stopPreview() { + if (mCamera != null) { + mCamera.stopPreview(); + mCamera.setPreviewCallback(null); + mCamera.release(); + mCamera = null; + } + if (mAvcCodec != null) { + mAvcCodec.stopEncoder(); + mAvcCodec = null; + } + } + + public void setOnRecvData(IVirtualDeviceDataListener listener) { + mCameraListener = listener; + } + + public void setResolution(short width, short height) { + mWidth = width == -1 ? DEFAULT_WIDTH : width; + mHeight = height == -1 ? DEFAULT_HEIGHT : height; + } + + public short getWidth() { + return (short) mWidth; + } + + public short getHeight() { + return (short) mHeight; + } + + public int getFacing() { + return cameraInfo.facing; + } + + public int getOrientation() { + return cameraInfo.orientation; + } + + public byte[] getParameters() { + return mParameters.flatten().getBytes(); + } + + public void setParameters(byte[] data) { + if (mParameters == null) { + return; + } + mParameters.unflatten(new String(data)); + } + + public void setFps(int fps) { + mFps = fps; + } + + public boolean isSupportH264() { + return AvcEncoder.isSupportH264(); + } + + @Override + public void onPreviewFrame(byte[] data, Camera camera) { + Camera.Size size = camera.getParameters().getPreviewSize(); + if (mIsUseH264) compressWithH264(data); + else compressWithMPEG(data, size.width, size.height); + if (mSurfaceTexture != null) { + camera.addCallbackBuffer(mPreviewBuffer); + } + } + + private void compressWithMPEG(byte[] data, int width, int height) { + YuvImage image = new YuvImage(data, ImageFormat.NV21, width, height, null); + jpegBuffer.reset(); + int quality = 50; + if (!image.compressToJpeg(new Rect(0, 0, width, height), quality, jpegBuffer)) { + Log.e(TAG, "compressWithMPEG: failed to compress to jpeg"); + return; + } + if (mCameraListener != null) { + byte[] jpegData = jpegBuffer.toByteArray(); + mCameraListener.onRecvData(jpegData, jpegData.length, mCameraId); + } + } + + private void compressWithH264(byte[] data) { + mAvcCodec.offerEncoder(data, mH264Buffer); + } + + private Camera.Parameters setDefaultParameters(Camera.Parameters param) { + Set supportFocusModes = new HashSet<>(Arrays.asList(FOCUS_MODE_AUTO + , FOCUS_MODE_CONTINUOUS_PICTURE, FOCUS_MODE_CONTINUOUS_VIDEO, FOCUS_MODE_FIXED + , FOCUS_MODE_INFINITY, FOCUS_MODE_MACRO)); + List focusModes = param.getSupportedFocusModes(); + + StringBuilder sb = new StringBuilder(); + for (String str : focusModes) { + if (supportFocusModes.contains(str)) { + sb.append(str + ","); + } + } + sb.deleteCharAt(sb.length() - 1); + param.set("focus-mode-values", sb.toString()); + + List picSize = param.getSupportedPictureSizes(); + sb = new StringBuilder(); + for (Camera.Size size : picSize) { + if (size.width < SIZE_WIDTH_LIMIT) { + sb.append(size.width + "x" + size.height + ","); + } + } + sb.deleteCharAt(sb.length() - 1); + param.set("picture-size-values", sb.toString()); + + List previewSize = param.getSupportedPreviewSizes(); + sb = new StringBuilder(); + for (Camera.Size size : previewSize) { + if (size.width < SIZE_WIDTH_LIMIT) { + sb.append(size.width + "x" + size.height + ","); + } + } + sb.deleteCharAt(sb.length() - 1); + param.set("preview-size-values", sb.toString()); + + List videoSize = param.getSupportedVideoSizes(); + sb = new StringBuilder(); + for (Camera.Size size : videoSize) { + if (size.width < SIZE_WIDTH_LIMIT) { + sb.append(size.width + "x" + size.height + ","); + } + } + sb.deleteCharAt(sb.length() - 1); + param.set("video-size-values", sb.toString()); + return param; + } + + class H264DataHandler implements AvcEncoderDataHandler { + @Override + public void handleData(byte[] data, int length) { + if (mCameraListener != null) mCameraListener.onRecvData(data, length, mCameraId); + } + } +} diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/VirtualCameraManager.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/VirtualCameraManager.java new file mode 100644 index 0000000..9da13d2 --- /dev/null +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/VirtualCameraManager.java @@ -0,0 +1,188 @@ +package com.huawei.cloudphone.virtualdevice.camera; + +import static com.huawei.cloudphone.jniwrapper.JNIWrapper.CAMERA_DATA; +import static com.huawei.cloudphone.virtualdevice.common.VirtualDeviceProtocol.MSG_HEADER_LEN; + +import android.util.Log; + +import com.huawei.cloudphone.virtualdevice.common.IVirtualDeviceDataListener; +import com.huawei.cloudphone.virtualdevice.common.VirtualDeviceManager; +import com.huawei.cloudphone.virtualdevice.common.VirtualDeviceProtocol; +import com.huawei.cloudphone.virtualdevice.common.VirtualDeviceProtocol.MsgHeader; + +public class VirtualCameraManager extends VirtualDeviceManager { + private static final String TAG = "VirtualCameraManager"; + + private static final short OPT_CAMERA_GET_PARAM_REQ = 0x1; + private static final short OPT_CAMERA_GET_PARAM_RSP = 0x1001; + private static final short OPT_CAMERA_SET_PARAM_REQ = 0x2; + private static final short OPT_CAMERA_SET_PARAM_RSP = 0x1002; + private static final short OPT_CAMERA_GET_INFO_REQ = 0x3; + private static final short OPT_CAMERA_GET_INFO_RSP = 0x1003; + private static final short OPT_CAMERA_START_PREVIEW_REQ = 0x4; + private static final short OPT_CAMERA_START_PREVIEW_RSP = 0x1004; + private static final short OPT_CAMERA_STOP_PREVIEW_REQ = 0x5; + private static final short OPT_CAMERA_STOP_PREVIEW_RSP = 0x1005; + private static final short OPT_CAMERA_IS_SUPPORT_H264_REQ = 0x6; + private static final short OPT_CAMERA_IS_SUPPORT_H264_RSP = 0x1006; + private static final short OPT_CAMERA_GET_SIZE_REQ = 0x7; + private static final short OPT_CAMERA_GET_SIZE_RSP = 0x1007; + private static final short OPT_CAMERA_FRAME = 0x8; + private static final short OPT_CAMERA_INVALID = 0x9; + + private static final int RSP_RESULT_LENGTH = 2; + private static final int RSP_INFO_LENGTH = 3; + private static final int RSP_SIZE_LENGTH = 4; + private static final int RSP_SUPPORT_H264_LENGTH = 1; + + private VirtualCamera mVirtualCamera; + private VirtualDeviceProtocol mVirtualDeviceProtocol; + + public VirtualCameraManager(VirtualDeviceProtocol virtualDeviceProtocol) { + mVirtualCamera = new VirtualCamera(); + mVirtualDeviceProtocol = virtualDeviceProtocol; + } + + public void stop() { + mVirtualCamera.stopPreview(); + } + + public void processMsg(MsgHeader header, byte[] body) { + switch (header.mOptType) { + case OPT_CAMERA_GET_PARAM_REQ: + Log.i(TAG, "processMsg: get param."); + handleGetParamReq(header, body); + break; + case OPT_CAMERA_SET_PARAM_REQ: + Log.i(TAG, "processMsg: set param."); + handleSetParamReq(header, body); + break; + case OPT_CAMERA_GET_INFO_REQ: + Log.i(TAG, "processMsg: get info."); + handleGetInfoReq(header, body); + break; + case OPT_CAMERA_START_PREVIEW_REQ: + Log.i(TAG, "processMsg: start preview."); + handleStartPreviewReq(header, body); + break; + case OPT_CAMERA_STOP_PREVIEW_REQ: + Log.i(TAG, "processMsg: stop preview."); + handleStopPreviewReq(header, body); + break; + case OPT_CAMERA_IS_SUPPORT_H264_REQ: + Log.i(TAG, "processMsg: is support h264."); + handleIsSupportH264Req(header, body); + break; + case OPT_CAMERA_GET_SIZE_REQ: + Log.i(TAG, "processMsg: get size."); + handleGetSizeReq(header, body); + break; + } + } + + private void handleGetParamReq(MsgHeader header, byte[] body) { + int result = mVirtualCamera.open(header.mDeviceId); + byte[] param = mVirtualCamera.getParameters(); + byte[] rspBody = new byte[param.length + RSP_RESULT_LENGTH]; + rspBody[0] = 0x0; + rspBody[1] = (byte) (result == 0 ? 0x0 : 0x1); + System.arraycopy(param, 0, rspBody, RSP_RESULT_LENGTH, param.length); + int rspMsgLen = MSG_HEADER_LEN + RSP_RESULT_LENGTH + param.length; + MsgHeader rspHeader = new MsgHeader(OPT_CAMERA_GET_PARAM_RSP, DEV_TYPE_CAMERA, header.mDeviceId, rspMsgLen); + mVirtualDeviceProtocol.sendMsg(rspHeader, rspBody, CAMERA_DATA); + } + + private void handleSetParamReq(MsgHeader header, byte[] body) { + mVirtualCamera.setParameters(body); + byte[] rspBody = new byte[RSP_RESULT_LENGTH]; + int rspMsgLen = MSG_HEADER_LEN + RSP_RESULT_LENGTH; + rspBody[0] = 0x0; + rspBody[1] = 0x0; + MsgHeader rspHeader = new MsgHeader(OPT_CAMERA_SET_PARAM_RSP, DEV_TYPE_CAMERA, header.mDeviceId, rspMsgLen); + mVirtualDeviceProtocol.sendMsg(rspHeader, rspBody, CAMERA_DATA); + } + + private void handleGetInfoReq(MsgHeader header, byte[] body) { + int face = mVirtualCamera.getFacing(); + int orientation = mVirtualCamera.getOrientation(); + byte[] rspBody = new byte[RSP_RESULT_LENGTH + RSP_INFO_LENGTH]; + rspBody[0] = 0x0; + rspBody[1] = 0x0; + rspBody[2] = (byte) (face & 0x00FF); + rspBody[3] = (byte) (orientation >> 8); + rspBody[4] = (byte) (orientation & 0x00FF); + int rspMsgLen = MSG_HEADER_LEN + RSP_RESULT_LENGTH + RSP_INFO_LENGTH; + MsgHeader rspHeader = new MsgHeader(OPT_CAMERA_GET_INFO_RSP, DEV_TYPE_CAMERA, header.mDeviceId, rspMsgLen); + mVirtualDeviceProtocol.sendMsg(rspHeader, rspBody, CAMERA_DATA); + } + + private void handleStartPreviewReq(MsgHeader header, byte[] body) { + short width = (short)((body[0] << 8) | (body[1] & 0x00FF)); + short height = (short)((body[2] << 8) | (body[3] & 0x00FF)); + int fps = (body[4] << 8) | (body[5] & 0x00FF); + + int result = mVirtualCamera.open(header.mDeviceId); + if (result == 0) { + mVirtualCamera.setOnRecvData(new CameraDataListener()); + mVirtualCamera.setResolution(width, height); + mVirtualCamera.setFps(fps); + result = mVirtualCamera.startPreview(); + } + byte[] rspBody = new byte[RSP_RESULT_LENGTH]; + rspBody[0] = 0x0; + rspBody[1] = (byte) (result == 0 ? 0x0 : 0x1); + int rspMsgLen = MSG_HEADER_LEN + RSP_RESULT_LENGTH; + MsgHeader rspHeader = new MsgHeader(OPT_CAMERA_START_PREVIEW_RSP, DEV_TYPE_CAMERA, header.mDeviceId, rspMsgLen); + mVirtualDeviceProtocol.sendMsg(rspHeader, rspBody, CAMERA_DATA); + } + + private void handleStopPreviewReq(MsgHeader header, byte[] body) { + mVirtualCamera.setOnRecvData(null); + mVirtualCamera.stopPreview(); + byte[] rspBody = new byte[RSP_RESULT_LENGTH]; + int rspMsgLen = MSG_HEADER_LEN + RSP_RESULT_LENGTH; + MsgHeader rspHeader = new MsgHeader(OPT_CAMERA_STOP_PREVIEW_RSP, DEV_TYPE_CAMERA, header.mDeviceId, rspMsgLen); + mVirtualDeviceProtocol.sendMsg(rspHeader, rspBody, CAMERA_DATA); + } + + private void handleIsSupportH264Req(MsgHeader header, byte[] body) { + boolean isSupportH264 = mVirtualCamera.isSupportH264(); + byte[] rspBody = new byte[RSP_RESULT_LENGTH + RSP_SUPPORT_H264_LENGTH]; + rspBody[2] = (byte) ((isSupportH264 ? 1 : 0) & 0x00FF); + int rspMsgLen = MSG_HEADER_LEN + RSP_SUPPORT_H264_LENGTH; + MsgHeader rspHeader = new MsgHeader(OPT_CAMERA_IS_SUPPORT_H264_RSP, DEV_TYPE_CAMERA, header.mDeviceId, rspMsgLen); + mVirtualDeviceProtocol.sendMsg(rspHeader, rspBody, CAMERA_DATA); + } + + private void handleGetSizeReq(MsgHeader header, byte[] body) { + int width = mVirtualCamera.getWidth(); + int height = mVirtualCamera.getHeight(); + + byte[] rspBody = new byte[RSP_RESULT_LENGTH + RSP_SIZE_LENGTH]; + rspBody[0] = 0x0; + rspBody[1] = 0x0; + rspBody[2] = (byte) (width >> 8); + rspBody[3] = (byte) (width & 0x00FF); + rspBody[4] = (byte) (height >> 8); + rspBody[5] = (byte) (height & 0x00FF); + int rspMsgLen = MSG_HEADER_LEN + RSP_RESULT_LENGTH + RSP_SIZE_LENGTH; + MsgHeader rspHeader = new MsgHeader(OPT_CAMERA_GET_SIZE_RSP, DEV_TYPE_CAMERA, header.mDeviceId, rspMsgLen); + mVirtualDeviceProtocol.sendMsg(rspHeader, rspBody, CAMERA_DATA); + } + + + class CameraDataListener implements IVirtualDeviceDataListener { + @Override + public void onRecvData(Object... args) { + byte[] data = (byte[]) args[0]; + int length = (int) args[1]; + int devId = (int) args[2]; + int repMsgLen = length + MSG_HEADER_LEN; + MsgHeader header = new MsgHeader(OPT_CAMERA_FRAME, DEV_TYPE_CAMERA, (short) devId, repMsgLen); + byte[] repBody = new byte[length]; + System.arraycopy(data, 0, repBody, 0, length); + mVirtualDeviceProtocol.sendMsg(header, repBody, CAMERA_DATA); + } + } + +} diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/IVirtualDeviceDataListener.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/IVirtualDeviceDataListener.java new file mode 100644 index 0000000..263f633 --- /dev/null +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/IVirtualDeviceDataListener.java @@ -0,0 +1,10 @@ +package com.huawei.cloudphone.virtualdevice.common; + +public interface IVirtualDeviceDataListener { + + /** + * 接受数据 + * @param args + */ + void onRecvData(Object...args); +} diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/IVirtualDeviceIO.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/IVirtualDeviceIO.java new file mode 100644 index 0000000..7880332 --- /dev/null +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/IVirtualDeviceIO.java @@ -0,0 +1,23 @@ +package com.huawei.cloudphone.virtualdevice.common; + +public interface IVirtualDeviceIO { + + /** + * 读取N个字节的数据 + * @param data + * @param offset + * @param length + * @return + */ + int readN(byte[] data, int offset, int length); + + /** + * 发送N个字节数据 + * @param data + * @param offset + * @param length + * @param deviceType + * @return + */ + int writeN(byte[] data, int offset, int length, int deviceType); +} diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/ParamBundle.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/ParamBundle.java new file mode 100644 index 0000000..c43342f --- /dev/null +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/ParamBundle.java @@ -0,0 +1,30 @@ +package com.huawei.cloudphone.virtualdevice.common; + +import android.content.Context; +import android.view.SurfaceHolder; + +public class ParamBundle { + static private Context mContext; + static private SurfaceHolder mSurfaceHolder; + + public static void setAppContext(Context context) { + mContext = context; + } + + public static void setSurfaceHolder(SurfaceHolder surfaceHolder) { + mSurfaceHolder = surfaceHolder; + } + + public static SurfaceHolder getSurfaceHolder() { + return mSurfaceHolder; + } + + public static Context getAppContext() { + return mContext; + } + + public static void resetParam() { + mContext = null; + mSurfaceHolder = null; + } +} diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/RingBuffer.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/RingBuffer.java new file mode 100644 index 0000000..ab087dd --- /dev/null +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/RingBuffer.java @@ -0,0 +1,72 @@ +package com.huawei.cloudphone.virtualdevice.common; + +import java.util.Arrays; + +public class RingBuffer { + private final static int DEFAULT_SIZE = 1024; + private Object[] buffer; + private int head = 0; + private int tail = 0; + private int bufferSize; + + public RingBuffer() { + this.bufferSize = DEFAULT_SIZE; + this.buffer = new Object[bufferSize]; + } + + public RingBuffer(int size) { + this.bufferSize = size; + this.buffer = new Object[bufferSize]; + } + + private Boolean isEmpty() { + return head == tail; + } + + private Boolean isFull() { + return (tail + 1) % bufferSize == head; + } + + public void clear() { + Arrays.fill(buffer, null); + this.head = 0; + this.tail = 0; + } + + public Boolean put(Object v) { + if (isFull()) { + return false; + } + buffer[tail] = v; + tail = (tail + 1) % bufferSize; + return true; + } + + public Object get() { + if (isEmpty()) { + return null; + } + Object result = buffer[head]; + head = (head + 1) % bufferSize; + return result; + } + + public Object[] getAll() { + if (isEmpty()) { + return new Object[0]; + } + int copyTail = tail; + int count = head < copyTail ? copyTail - head : bufferSize - head + copyTail; + Object[] result = new String[count]; + if (head < copyTail) { + if (copyTail - head >= 0) + System.arraycopy(buffer, head, result, 0, copyTail - head); + } else { + if (bufferSize - head >= 0) + System.arraycopy(buffer, head, result, 0, bufferSize - head); + if (copyTail >= 0) System.arraycopy(buffer, 0, result, bufferSize - head, copyTail); + } + head = copyTail; + return result; + } +} diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/RingBufferVirtualDeviceIO.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/RingBufferVirtualDeviceIO.java new file mode 100644 index 0000000..faf8879 --- /dev/null +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/RingBufferVirtualDeviceIO.java @@ -0,0 +1,63 @@ +package com.huawei.cloudphone.virtualdevice.common; + +import static com.huawei.cloudphone.jniwrapper.JNIWrapper.CAMERA_DATA; +import static com.huawei.cloudphone.jniwrapper.JNIWrapper.MICROPHONE_DATA; + +import com.huawei.cloudphone.api.CloudPhoneManager; + +import java.nio.ByteBuffer; + +public class RingBufferVirtualDeviceIO implements IVirtualDeviceIO{ + private RingBuffer mRingBuffer; + private int mDataLen; + private int mDataOffset; + private byte[] mDataBuffer; + private Object mObjectLock = new Object(); + private static final int VIRTUAL_CAMERA = 0; + private static final int VIRTUAL_MICROPHONE = 1; + private static final int VIRTUAL_SENSOR = 2; + + public RingBufferVirtualDeviceIO() { + mDataOffset = 0; + mDataBuffer = null; + mDataLen = 0; + mRingBuffer = new RingBuffer(); + } + + + @Override + public int readN(byte[] data, int offset, int length) { + int ret; + synchronized (mObjectLock) { + if (mDataOffset < mDataLen && mDataBuffer != null) { + int oneTimeReadLength = Math.min(length, mDataLen - mDataOffset); + System.arraycopy(mDataBuffer, mDataOffset, data, offset, oneTimeReadLength); + mDataOffset += oneTimeReadLength; + ret = oneTimeReadLength; + } else { + ByteBuffer byteBuffer = (ByteBuffer) mRingBuffer.get(); + if (byteBuffer != null) { + mDataBuffer = new byte[byteBuffer.capacity()]; + byteBuffer.get(mDataBuffer, 0, mDataBuffer.length); + mDataOffset = 0; + mDataLen = mDataBuffer.length; + } + ret = 0; + } + } + return ret; + } + + @Override + public int writeN(byte[] data, int offset, int length, int deviceType) { + synchronized (mObjectLock) { + CloudPhoneManager.createCloudPhoneInstance().sendVirtualDeviceData((byte) deviceType, data); + } + return length; + } + + public void fillData(byte[] data) { + ByteBuffer buffer = ByteBuffer.wrap(data); + mRingBuffer.put(buffer); + } +} diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/VirtualDeviceManager.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/VirtualDeviceManager.java new file mode 100644 index 0000000..45d7e33 --- /dev/null +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/VirtualDeviceManager.java @@ -0,0 +1,18 @@ +package com.huawei.cloudphone.virtualdevice.common; + +import com.huawei.cloudphone.virtualdevice.common.VirtualDeviceProtocol.MsgHeader; + +public class VirtualDeviceManager { + public static final short DEV_TYPE_CAMERA = 1; + public static final short DEV_TYPE_MICROPHONE = 2; + public static final short DEV_TYPE_SENSOR = 0; + + public void processMsg(MsgHeader header, byte[] body) { + } + + public void start() { + } + + public void stop() { + } +} diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/VirtualDeviceProtocol.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/VirtualDeviceProtocol.java new file mode 100644 index 0000000..ffcf03b --- /dev/null +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/VirtualDeviceProtocol.java @@ -0,0 +1,219 @@ +package com.huawei.cloudphone.virtualdevice.common; + +import static com.huawei.cloudphone.virtualdevice.camera.VirtualCameraManager.DEV_TYPE_CAMERA; +import static com.huawei.cloudphone.virtualdevice.common.VirtualDeviceManager.DEV_TYPE_MICROPHONE; +import static com.huawei.cloudphone.virtualdevice.common.VirtualDeviceManager.DEV_TYPE_SENSOR; + +import android.hardware.SensorManager; +import android.util.Log; + +import com.huawei.cloudphone.virtualdevice.camera.VirtualCameraManager; +import com.huawei.cloudphone.virtualdevice.microphone.VirtualMicrophoneManager; +import com.huawei.cloudphone.virtualdevice.sensor.VirtualSensorManager; + +import java.util.HashMap; +import java.util.Map; + +public class VirtualDeviceProtocol { + private static final String TAG = "VirtualDeviceProtocol"; + public static int MSG_HEADER_LEN = 16; + public static int WAITING_INTERVAL = 200; + private IVirtualDeviceIO mVirtualDeviceIO; + private boolean mIsTaskRun; + private PacketParseThread mPktProcessThread; + static Object mSendMsgLock = new Object(); + private Map virtualDeviceManagers; + + public VirtualDeviceProtocol(IVirtualDeviceIO virtualDeviceIO ,SensorManager sensorManager) { + mVirtualDeviceIO = virtualDeviceIO; + initVirtualDeviceManagers(sensorManager); + } + + private void initVirtualDeviceManagers(SensorManager sensorManager) { + virtualDeviceManagers = new HashMap(); + virtualDeviceManagers.put(DEV_TYPE_CAMERA, new VirtualCameraManager(this)); + virtualDeviceManagers.put(DEV_TYPE_MICROPHONE, new VirtualMicrophoneManager(this)); + virtualDeviceManagers.put(DEV_TYPE_SENSOR, new VirtualSensorManager(this, sensorManager)); + } + + public void processMsg(MsgHeader header, byte[] body) { + VirtualDeviceManager virtualDeviceManager = virtualDeviceManagers.get(header.mDeviceType); + if (virtualDeviceManager == null) { + Log.e(TAG, "processMsg: Error msg type :" + header.mDeviceType); + return; + } + virtualDeviceManager.processMsg(header, body); + } + + + public void startProcess() { + mIsTaskRun = true; + mPktProcessThread = new PacketParseThread(); + mPktProcessThread.start(); + for (Map.Entry entry : virtualDeviceManagers.entrySet()) { + entry.getValue().start(); + } + } + + public void stopProcess() { + mIsTaskRun = false; + try { + mPktProcessThread.join(); + } catch (InterruptedException e) { + Log.e(TAG, "stopProcess: failed to stop mPktProcessThread.", e); + } + for (Map.Entry entry : virtualDeviceManagers.entrySet()) { + entry.getValue().stop(); + } + } + + public void sendMsg(MsgHeader header, byte[] body, int deviceType) { + synchronized (mSendMsgLock) { + byte[] headerData = header.getData(); + writeN(headerData, 0, headerData.length, deviceType); + if(body != null) { + writeN(body, 0, body.length, deviceType); + } + } + } + + private int readN(byte[] data, int offset, int len) { + int readLen = 0; + while(len > 0) { + if(!mIsTaskRun) { + break; + } + int retLen = mVirtualDeviceIO.readN(data, offset, len); + if(retLen > 0) { + len -= retLen; + offset += retLen; + readLen += retLen; + continue; + } + try { + Thread.sleep(WAITING_INTERVAL); + } catch (InterruptedException e) { + Log.e(TAG, "readN: sleep is interrupt", e); + } + } + return readLen; + } + + private synchronized int writeN(byte[] data, int offset, int len, int deviceType) { + int writeLen = 0; + while(len > 0) { + if(!mIsTaskRun) { + break; + } + int retLen = mVirtualDeviceIO.writeN(data, offset, len, deviceType); + if(retLen > 0) { + len -= retLen; + offset += retLen; + writeLen += retLen; + continue; + } + try { + Thread.sleep(WAITING_INTERVAL); + } catch (InterruptedException e) { + Log.e(TAG, "readN: sleep is interrupt", e); + } + } + return writeLen; + } + + class PacketParseThread extends Thread { + byte[] header = new byte[MSG_HEADER_LEN]; + @Override + public void run(){ + while (mIsTaskRun) { + if (readN(header, 0, MSG_HEADER_LEN) != MSG_HEADER_LEN) { + Log.e(TAG, "Read msg header error"); + continue; + } + MsgHeader msgHeader = new MsgHeader(header); + int bodyLen = msgHeader.mPayloadLength; + byte[] body = new byte[bodyLen]; + if (readN(body, 0, bodyLen) != bodyLen) { + Log.e(TAG, "Read msg header error"); + continue; + } + processMsg(msgHeader, body); + } + } + } + + public static class MsgHeader { + public short mVersion; + public short mOptType; + public short mDeviceType; + public short mDeviceId; + public short mNextProtocol; + public short mHopLimit; + public int mPayloadLength; + private byte[] mData = null; + + public MsgHeader(byte[] data) { + initParam(data); + mData = new byte[data.length]; + System.arraycopy(data, 0, mData, 0, data.length); + } + + public MsgHeader(short optType, short devType, short devId, int msgLen) { + mData = new byte[MSG_HEADER_LEN]; + mData[0] = 0; + mData[1] = 0x01; + + mData[2] = (byte) (optType >> 8); + mData[3] = (byte) (optType & 0x00FF); + + mData[4] = (byte) (devType >> 8); + mData[5] = (byte) (devType & 0x00FF); + + mData[6] = (byte) (devId >> 8); + mData[7] = (byte) (devId & 0x00FF); + + mData[8] = (byte) ((msgLen & 0xFF000000) >> 24); + mData[9] = (byte) ((msgLen & 0xFF000000) >> 16); + mData[10] = (byte) ((msgLen & 0xFF000000) >> 8); + mData[11] = (byte) (msgLen & 0xFF000000); + + mData[12] = 0x00; + mData[13] = 0x00; + + mData[14] = 0x00; + mData[15] = 0x00; + + initParam(mData); + } + + private void initParam(byte[] data) { + mVersion = (short) ((data[0] << 8) | (data[1] & 0x0FF)); + mOptType = (short) ((data[2] << 8) | (data[3] & 0x0FF)); + mDeviceType = (short) ((data[4] << 8) | (data[5] & 0x0FF)); + mDeviceId = (short) ((data[6] << 8) | (data[7] & 0x0FF)); + mPayloadLength = ((data[8] << 24) | ((data[9] & 0x0FF) << 16) + | ((data[10] & 0x0FF) << 8) | (data[11] & 0x0FF)) - MSG_HEADER_LEN; + mNextProtocol = (short) ((data[12] << 8) | (data[13] & 0x0FF)); + mHopLimit = (short) ((data[14] << 8) | (data[15] & 0x0FF)); + } + + public byte[] getData() { + return mData; + } + + @Override + public String toString() { + return "MsgHeader{" + + "mVersion=" + mVersion + + ", mOptType=" + mOptType + + ", mDeviceType=" + mDeviceType + + ", mDeviceId=" + mDeviceId + + ", mPayloadLength=" + mPayloadLength + + ", mNextProtocol=" + mNextProtocol + + ", mHopLimit=" + mHopLimit + + "}"; + } + } +} + + diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/microphone/VirtualMicrophone.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/microphone/VirtualMicrophone.java new file mode 100644 index 0000000..bf41dea --- /dev/null +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/microphone/VirtualMicrophone.java @@ -0,0 +1,125 @@ +package com.huawei.cloudphone.virtualdevice.microphone; + +import android.media.AudioFormat; +import android.media.AudioRecord; +import android.media.MediaRecorder; +import android.util.Log; + +import com.huawei.cloudphone.jniwrapper.OpusJNIWrapper; +import com.huawei.cloudphone.virtualdevice.common.IVirtualDeviceDataListener; + +import java.util.Map; +import java.util.Objects; + +public class VirtualMicrophone { + private static final String TAG = "VirtualMicrophone"; + private static final int AUDIO_INPUT = MediaRecorder.AudioSource.MIC; + static final int DEFAULT_SAMPLE_RATE = 48000; + static final int DEFAULT_CHANNEL = 1; + static final int DEFAULT_BUFFER_SIZE = 320; + static final String SAMPLE_RATE = "sample_rate"; + static final String MAX_FRAME_SIZE = "max_frame_size"; + static final String CHANNEL = "channel"; + static final String FORMAT = "format"; + + private AudioRecord mAudioRecord; + private int mRecordBuffSize = 320; + private int mMaxFrameSize; + private int mSampleRate; + private int mChannel; + private int mSampleFormat; + private boolean mIsReadTaskRunning; + private ReadThread mThread; + private IVirtualDeviceDataListener mListener; + private boolean mIsStart = false; + + public VirtualMicrophone() { + mAudioRecord = null; + mRecordBuffSize = DEFAULT_BUFFER_SIZE; + mSampleRate = DEFAULT_SAMPLE_RATE; + mChannel = DEFAULT_CHANNEL; + mSampleFormat = AudioFormat.ENCODING_PCM_16BIT; + mIsReadTaskRunning = false; + mListener = null; + } + + public int start() { + if (mIsStart) return 0; + if (OpusJNIWrapper.createOpusEncoder(mSampleRate, DEFAULT_CHANNEL) != 0) { + return -1; + } + mRecordBuffSize = AudioRecord.getMinBufferSize(mSampleRate, mChannel, AudioFormat.ENCODING_PCM_16BIT); + mAudioRecord = new AudioRecord(AUDIO_INPUT, mSampleRate, mChannel, mSampleFormat, mRecordBuffSize); + try { + mAudioRecord.startRecording(); + } catch (IllegalStateException e) { + Log.e(TAG, "start failed, ", e); + return -1; + } + mIsReadTaskRunning = true; + mThread = new ReadThread(); + mThread.start(); + mIsStart = true; + return 0; + } + + public int stop() { + if (!mIsStart) return 0; + mIsReadTaskRunning = false; + try { + if (mThread != null) { + mThread.join(); + mThread = null; + } + mAudioRecord.stop(); + } catch (IllegalStateException | InterruptedException e) { + Log.e(TAG, "stop failed, ", e); + return -1; + } + + mAudioRecord.release(); + mAudioRecord = null; + OpusJNIWrapper.destroyOpusEncoder(); + mIsStart = false; + return 0; + } + + public void setOnRecvDataListener(IVirtualDeviceDataListener listener) { + mListener = listener; + } + + public int setParameters(Map params) { + try { + mSampleRate = Integer.parseInt(Objects.requireNonNull(params.get(SAMPLE_RATE))); + if (mSampleRate < 0 ) mSampleFormat = DEFAULT_SAMPLE_RATE; + mMaxFrameSize = Integer.parseInt(Objects.requireNonNull(params.get(MAX_FRAME_SIZE))); + mChannel = Integer.parseInt(Objects.requireNonNull(params.get(CHANNEL))); + if (Integer.parseInt(Objects.requireNonNull(params.get(FORMAT))) == 1) + mSampleFormat = AudioFormat.ENCODING_PCM_16BIT; + } catch (RuntimeException e) { + Log.e(TAG, "setParameters: failed to set param", e); + return -1; + } + Log.i(TAG, "mSampleRate = " + mSampleRate + ", mMaxFrameSize = " + mMaxFrameSize + + ", mChannel = " + mChannel + ", mSampleFormat = " + mSampleFormat); + return 0; + } + + class ReadThread extends Thread { + @Override + public void run() { + short[] bytes = new short[mRecordBuffSize]; + byte[] outBuff = new byte[mRecordBuffSize]; + while (mIsReadTaskRunning) { + mAudioRecord.read(bytes, 0, bytes.length); + if (mListener != null) { + int outBufLen = OpusJNIWrapper.opusEncode(bytes, bytes.length, outBuff); + if (outBufLen > 0) { + mListener.onRecvData(outBuff, 0, outBufLen); + } + } + } + } + } + +} diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/microphone/VirtualMicrophoneManager.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/microphone/VirtualMicrophoneManager.java new file mode 100644 index 0000000..4eec2c3 --- /dev/null +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/microphone/VirtualMicrophoneManager.java @@ -0,0 +1,127 @@ +package com.huawei.cloudphone.virtualdevice.microphone; + +import static com.huawei.cloudphone.jniwrapper.JNIWrapper.CAMERA_DATA; +import static com.huawei.cloudphone.jniwrapper.JNIWrapper.MICROPHONE_DATA; +import static com.huawei.cloudphone.virtualdevice.common.VirtualDeviceProtocol.MSG_HEADER_LEN; +import static com.huawei.cloudphone.virtualdevice.microphone.VirtualMicrophone.CHANNEL; +import static com.huawei.cloudphone.virtualdevice.microphone.VirtualMicrophone.DEFAULT_SAMPLE_RATE; +import static com.huawei.cloudphone.virtualdevice.microphone.VirtualMicrophone.FORMAT; +import static com.huawei.cloudphone.virtualdevice.microphone.VirtualMicrophone.MAX_FRAME_SIZE; +import static com.huawei.cloudphone.virtualdevice.microphone.VirtualMicrophone.SAMPLE_RATE; + +import android.util.Log; + +import com.huawei.cloudphone.virtualdevice.common.IVirtualDeviceDataListener; +import com.huawei.cloudphone.virtualdevice.common.VirtualDeviceManager; +import com.huawei.cloudphone.virtualdevice.common.VirtualDeviceProtocol; +import com.huawei.cloudphone.virtualdevice.common.VirtualDeviceProtocol.MsgHeader; + +import java.util.HashMap; +import java.util.Map; + +public class VirtualMicrophoneManager extends VirtualDeviceManager { + private static final String TAG = "VirtualMICManager"; + private static final short DEV_MIC_ID = 0; + + private static final short OPT_MICROPHONE_START_RECORD_REQ = 0x1; + private static final short OPT_MICROPHONE_START_RECORD_RSP = 0x1001; + private static final short OPT_MICROPHONE_STOP_RECORD_REQ = 0x2; + private static final short OPT_MICROPHONE_STOP_RECORD_RSP = 0x1002; + private static final short OPT_MICROPHONE_SET_PARAM_REQ = 0x3; + private static final short OPT_MICROPHONE_SET_PARAM_RSP = 0x1003; + private static final short OPT_MICROPHONE_DATA = 0x10; + + private static final int RSP_RESULT_LENGTH = 2; + private static final int RSP_AUDIO_TYPE_LENGTH = 2; + + private VirtualMicrophone mVirtualMicrophone; + private VirtualDeviceProtocol mVirtualDeviceProtocol; + + public VirtualMicrophoneManager(VirtualDeviceProtocol virtualDeviceProtocol) { + mVirtualDeviceProtocol = virtualDeviceProtocol; + mVirtualMicrophone = new VirtualMicrophone(); + } + + public void processMsg(MsgHeader header, byte[] msgBody) { + switch (header.mOptType) { + case OPT_MICROPHONE_START_RECORD_REQ: + Log.i(TAG, "processMsg: start record"); + handleStartRecordReq(msgBody); + break; + case OPT_MICROPHONE_STOP_RECORD_REQ: + Log.i(TAG, "processMsg: stop record"); + handleStopRecordReq(msgBody); + break; + case OPT_MICROPHONE_SET_PARAM_REQ: + Log.i(TAG, "processMsg: set param"); + handleSetParamReq(msgBody); + break; + } + } + + private void handleStartRecordReq(byte[] msgBody) { + mVirtualMicrophone.setOnRecvDataListener(new MicrophoneDataListener()); + int result = mVirtualMicrophone.start(); + byte[] rspBody = new byte[RSP_RESULT_LENGTH + RSP_AUDIO_TYPE_LENGTH]; + rspBody[0] = 0x0; + rspBody[1] = (byte) (result == 0 ? 0x0 : 0x1); + rspBody[2] = 0x00; + rspBody[3] = 0x01; + int rspMsgLen = MSG_HEADER_LEN + RSP_RESULT_LENGTH + RSP_AUDIO_TYPE_LENGTH; + MsgHeader rspHeader = new MsgHeader(OPT_MICROPHONE_START_RECORD_RSP, DEV_TYPE_MICROPHONE, DEV_MIC_ID, rspMsgLen); + mVirtualDeviceProtocol.sendMsg(rspHeader, rspBody, MICROPHONE_DATA); + } + + private void handleStopRecordReq(byte[] msgBody) { + int result = mVirtualMicrophone.stop(); + int rspMsgLen = MSG_HEADER_LEN + RSP_RESULT_LENGTH; + byte[] rspBody = new byte[RSP_RESULT_LENGTH]; + rspBody[0] = 0x0; + rspBody[1] = (byte) (result == 0 ? 0x0 : 0x1); + MsgHeader rspHeader = new MsgHeader(OPT_MICROPHONE_STOP_RECORD_RSP, DEV_TYPE_MICROPHONE, DEV_MIC_ID, rspMsgLen); + mVirtualDeviceProtocol.sendMsg(rspHeader, rspBody, MICROPHONE_DATA); + } + + private void handleSetParamReq(byte[] msgBody) { + int maxFrameSize = (msgBody[0] << 8) | (msgBody[1] & 0x0FF); + int format = (msgBody[2] << 8) | (msgBody[3] & 0x0FF); + int sampleRate = (msgBody[4] << 8) | (msgBody[5] & 0x0FF); + int channel = (msgBody[6] << 8) | (msgBody[7] & 0x0FF); + Log.i(TAG, "mSampleRate = " + sampleRate + ", mMaxFrameSize = " + maxFrameSize + + ", mChannel = " + channel + ", mSampleFormat = " + format); + + Map paramMap = new HashMap<>(); + paramMap.put(FORMAT, Integer.toString(format)); + paramMap.put(MAX_FRAME_SIZE, Integer.toString(maxFrameSize)); + paramMap.put(SAMPLE_RATE, Integer.toString(sampleRate)); + paramMap.put(CHANNEL, Integer.toString(channel)); + + int result = mVirtualMicrophone.setParameters(paramMap); + byte[] rspBody = new byte[RSP_RESULT_LENGTH]; + rspBody[0] = 0x0; + rspBody[1] = (byte) (result == 0 ? 0x0 : 0x1); + + int rspMsgLen = MSG_HEADER_LEN + RSP_RESULT_LENGTH; + MsgHeader header = new MsgHeader(OPT_MICROPHONE_SET_PARAM_RSP, DEV_TYPE_MICROPHONE, DEV_MIC_ID, rspMsgLen); + mVirtualDeviceProtocol.sendMsg(header, rspBody, MICROPHONE_DATA); + } + + public void stop() { + mVirtualMicrophone.stop(); + } + + public class MicrophoneDataListener implements IVirtualDeviceDataListener { + + @Override + public void onRecvData(Object... args) { + byte[] data = (byte[]) args[0]; + int offset = (int) args[1]; + int length = (int) args[2]; + int rspMsgLen = length + MSG_HEADER_LEN; + MsgHeader header = new MsgHeader(OPT_MICROPHONE_DATA, DEV_TYPE_MICROPHONE, DEV_MIC_ID, rspMsgLen); + byte[] rspBody = new byte[length]; + System.arraycopy(data, offset, rspBody, 0, length); + mVirtualDeviceProtocol.sendMsg(header, rspBody, MICROPHONE_DATA); + } + } +} diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/sensor/VirtualSensor.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/sensor/VirtualSensor.java new file mode 100644 index 0000000..452cfbb --- /dev/null +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/sensor/VirtualSensor.java @@ -0,0 +1,79 @@ +package com.huawei.cloudphone.virtualdevice.sensor; + +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.Handler; +import android.os.HandlerThread; +import android.util.Log; + +import com.huawei.cloudphone.virtualdevice.common.IVirtualDeviceDataListener; + +public class VirtualSensor implements SensorEventListener { + private static final String TAG = "VirtualSensor"; + private SensorManager mSensorManager; + private Sensor mSensor; + private Sensor mAccelerationSensor; + private IVirtualDeviceDataListener mListener = null; + private HandlerThread mHandlerThread; + private boolean mIsStart = false; + + public VirtualSensor(SensorManager sensorManager) { + mSensorManager = sensorManager; + mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE); + mAccelerationSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); + } + + void registerSensorDataListener(IVirtualDeviceDataListener listener) { + mListener = listener; + } + + public void startProcess() { + Log.i(TAG, "startProcess"); + if (mIsStart) { + return; + } + mHandlerThread = new HandlerThread("sensorThread"); + mHandlerThread.start(); + Handler handler = new Handler(mHandlerThread.getLooper()); + mSensorManager.registerListener(this, mSensor, SensorManager.SENSOR_DELAY_GAME, handler); + mSensorManager.registerListener(this, mAccelerationSensor, SensorManager.SENSOR_DELAY_GAME, handler); + mIsStart = true; + } + + public void stopProcess() { + Log.i(TAG, "stopProcess"); + if (!mIsStart) { + return; + } + mSensorManager.unregisterListener(this, mSensor); + mSensorManager.unregisterListener(this, mAccelerationSensor); + try { + mHandlerThread.join(); + } catch (InterruptedException e) { + Log.e(TAG, "stopProcess: failed to stop mHandlerThread", e); + } + mIsStart = false; + } + + @Override + public void onSensorChanged(SensorEvent sensorEvent) { + if (mListener == null) { + return; + } + float x = sensorEvent.values[0]; + float y = sensorEvent.values[1]; + float z = sensorEvent.values[2]; + int type = sensorEvent.sensor.getType(); + if (type == Sensor.TYPE_ACCELEROMETER) { + mListener.onRecvData(x, y, z, sensorEvent.accuracy, 0); + } else if (type == Sensor.TYPE_GYROSCOPE) { + mListener.onRecvData(x, y, z, sensorEvent.accuracy, 1); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int i) { + } +} diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/sensor/VirtualSensorManager.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/sensor/VirtualSensorManager.java new file mode 100644 index 0000000..ef04cec --- /dev/null +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/sensor/VirtualSensorManager.java @@ -0,0 +1,83 @@ +package com.huawei.cloudphone.virtualdevice.sensor; + +import static com.huawei.cloudphone.jniwrapper.JNIWrapper.SENSOR_DATA; +import static com.huawei.cloudphone.virtualdevice.common.VirtualDeviceProtocol.MSG_HEADER_LEN; + +import android.hardware.SensorManager; +import android.util.Log; + +import com.huawei.cloudphone.virtualdevice.common.IVirtualDeviceDataListener; +import com.huawei.cloudphone.virtualdevice.common.VirtualDeviceManager; +import com.huawei.cloudphone.virtualdevice.common.VirtualDeviceProtocol; +import com.huawei.cloudphone.virtualdevice.common.VirtualDeviceProtocol.MsgHeader; + +public class VirtualSensorManager extends VirtualDeviceManager { + private static final String TAG = "VirtualSensorManager"; + + public static final short DEV_GYROSCOPE = 0x0; + public static final short DEV_ACCELERATION = 0x1; + public static final short OPT_SENSOR_ENABLE_REQ = 0x1; + public static final short OPT_SENSOR_ENABLE_RSP = 0x1001; + public static final short OPT_SENSOR_DISABLE_REQ = 0x2; + public static final short OPT_SENSOR_DISABLE_RSP = 0x1002; + public static final short OPT_SENSOR_DATA = 0x10; + + private VirtualSensor mVirtualSensor; + private VirtualDeviceProtocol mVirtualDeviceProtocol; + + public VirtualSensorManager(VirtualDeviceProtocol virtualDeviceProtocol, SensorManager sensorManager) { + mVirtualDeviceProtocol = virtualDeviceProtocol; + mVirtualSensor = new VirtualSensor(sensorManager); + } + + public void processMsg(MsgHeader header, byte[] body) { + switch (header.mOptType) { + case OPT_SENSOR_ENABLE_RSP: + Log.i(TAG, "processMsg: enable sensor"); + mVirtualSensor.registerSensorDataListener(new SensorDataListener()); + mVirtualSensor.startProcess(); + break; + case OPT_SENSOR_DISABLE_RSP: + Log.i(TAG, "processMsg: disable sensor"); + mVirtualSensor.stopProcess(); + break; + default: + Log.e(TAG, "processMsg: error opt type"); + } + } + + public void start() { + int rspMsgLen = MSG_HEADER_LEN; + MsgHeader header = new MsgHeader(OPT_SENSOR_ENABLE_REQ, DEV_TYPE_SENSOR, DEV_GYROSCOPE, rspMsgLen); + mVirtualDeviceProtocol.sendMsg(header, null, SENSOR_DATA); + header = new MsgHeader(OPT_SENSOR_ENABLE_REQ, DEV_TYPE_SENSOR, DEV_ACCELERATION, rspMsgLen); + mVirtualDeviceProtocol.sendMsg(header, null, SENSOR_DATA); + } + + public void stop() { + int rspMsgLen = MSG_HEADER_LEN; + MsgHeader header = new MsgHeader(OPT_SENSOR_DISABLE_REQ, DEV_TYPE_SENSOR, DEV_GYROSCOPE, rspMsgLen); + mVirtualDeviceProtocol.sendMsg(header, null, SENSOR_DATA); + header = new MsgHeader(OPT_SENSOR_DISABLE_REQ, DEV_TYPE_SENSOR, DEV_ACCELERATION, rspMsgLen); + mVirtualDeviceProtocol.sendMsg(header, null, SENSOR_DATA); + } + + class SensorDataListener implements IVirtualDeviceDataListener { + @Override + public void onRecvData(Object... args) { + String xStr = Float.valueOf((float) args[0]).toString(); + String yStr = Float.valueOf((float) args[1]).toString(); + String zStr = Float.valueOf((float) args[2]).toString(); + String accurateStr = Integer.valueOf((int) args[4]).toString(); + String body = xStr + ":" + yStr + ":" + zStr + ":" + accurateStr + ":"; + short type = (short) args[4]; + int bodyLen = body.getBytes().length; + int rspMsgLen = bodyLen + MSG_HEADER_LEN; + MsgHeader header = new MsgHeader(OPT_SENSOR_DATA, DEV_TYPE_SENSOR, type, rspMsgLen); + byte[] rspBody = new byte[bodyLen]; + System.arraycopy(body.getBytes(), 0, rspBody, 0, bodyLen); + mVirtualDeviceProtocol.sendMsg(header, rspBody, SENSOR_DATA); + + } + } +} -- Gitee From 10d8e0805ce69884b85530b5754d7e6fff654856 Mon Sep 17 00:00:00 2001 From: wangshuo <584363327@qq.com> Date: Mon, 29 May 2023 14:38:26 +0800 Subject: [PATCH 2/9] =?UTF-8?q?android=20sdk=E6=94=AF=E6=8C=81=E8=99=9A?= =?UTF-8?q?=E6=8B=9F=E4=BB=BF=E7=9C=9F=E8=AE=BE=E5=A4=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cloudapp/ui/CasCloudPhoneActivity.java | 33 ++++------ .../virtualdevice/VirtualDeviceSession.java | 2 +- .../virtualdevice/camera/AvcEncoder.java | 14 ++--- .../virtualdevice/camera/VirtualCamera.java | 10 +-- .../camera/VirtualCameraManager.java | 63 +++++++++++++------ .../common/VirtualDeviceProtocol.java | 49 +++++++++++---- .../microphone/VirtualMicrophoneManager.java | 38 +++++++---- .../sensor/VirtualSensorManager.java | 6 +- 8 files changed, 137 insertions(+), 78 deletions(-) diff --git a/app/src/main/java/com/huawei/cloudapp/ui/CasCloudPhoneActivity.java b/app/src/main/java/com/huawei/cloudapp/ui/CasCloudPhoneActivity.java index a253e4c..cd098ab 100644 --- a/app/src/main/java/com/huawei/cloudapp/ui/CasCloudPhoneActivity.java +++ b/app/src/main/java/com/huawei/cloudapp/ui/CasCloudPhoneActivity.java @@ -88,6 +88,10 @@ import static com.huawei.cloudapp.utils.CasConstantsUtil.USER_ID; import static com.huawei.cloudphone.api.CloudPhoneParas.DevType.DEV_PHONE; import static com.huawei.cloudphone.utils.CasConstantsUtil.VIRTUAL_HEIGHT; import static com.huawei.cloudphone.utils.CasConstantsUtil.VIRTUAL_WIDTH; +import static com.huawei.cloudphone.virtualdevice.camera.VirtualCameraManager.GRANT_CAMERA_PERMISSION_SUCCESS_ACTION; +import static com.huawei.cloudphone.virtualdevice.common.VirtualDeviceManager.DEV_TYPE_CAMERA; +import static com.huawei.cloudphone.virtualdevice.common.VirtualDeviceManager.DEV_TYPE_MICROPHONE; +import static com.huawei.cloudphone.virtualdevice.microphone.VirtualMicrophoneManager.GRANT_MICROPHONE_PERMISSION_SUCCESS_ACTION; public class CasCloudPhoneActivity extends FragmentActivity implements View.OnClickListener { private static final String TAG = "CasMainActivity"; @@ -249,7 +253,6 @@ public class CasCloudPhoneActivity extends FragmentActivity implements View.OnCl keepScreenLongLight(this, true); getIntentData(); initVirtualDeviceSession(); - getPermissions(); // get views loadingView = (ImageView) findViewById(R.id.loadingView); @@ -388,11 +391,16 @@ public class CasCloudPhoneActivity extends FragmentActivity implements View.OnCl @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - if (requestCode == CAMERA_PERMISSION_REQUEST_CODE || requestCode == RECORD_AUDIO_PERMISSION_REQUEST_CODE) { - isGotCameraAndRecordPermission = (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) & isGotCameraAndRecordPermission; - } super.onRequestPermissionsResult(requestCode, permissions, grantResults); - + if (requestCode == DEV_TYPE_CAMERA) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + this.sendBroadcast(new Intent(GRANT_CAMERA_PERMISSION_SUCCESS_ACTION)); + } + } else if (requestCode == DEV_TYPE_MICROPHONE) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + this.sendBroadcast(new Intent(GRANT_MICROPHONE_PERMISSION_SUCCESS_ACTION)); + } + } } /** @@ -418,21 +426,6 @@ public class CasCloudPhoneActivity extends FragmentActivity implements View.OnCl mVirtualDeviceSession.setVirtualDeviceIoHook(mRingBufferVirtualDeviceIO); } - private void getPermissions() { - if (Build.VERSION.SDK_INT < LOLLIPOP_MR1) return; - if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions(this, - new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE} - , CAMERA_PERMISSION_REQUEST_CODE); - } - if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions(this, - new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE} - , RECORD_AUDIO_PERMISSION_REQUEST_CODE); - } - - } - private void processStateChangeMsg(int code) { CASLog.i(TAG, "processStateChangeMsg code = " + code); switch (code) { diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/VirtualDeviceSession.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/VirtualDeviceSession.java index 8501357..1f4c464 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/VirtualDeviceSession.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/VirtualDeviceSession.java @@ -29,7 +29,7 @@ public class VirtualDeviceSession { } public void start() { - mVirtualDevice = new VirtualDeviceProtocol(mVirtualDeviceIO, (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE)); + mVirtualDevice = new VirtualDeviceProtocol(mVirtualDeviceIO, mContext); mVirtualDevice.startProcess(); } diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/AvcEncoder.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/AvcEncoder.java index b036237..bd51c22 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/AvcEncoder.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/AvcEncoder.java @@ -67,8 +67,8 @@ public class AvcEncoder { } } - public void offerEncoder(byte[] frame, byte[] outBuff) { - if ((frame == null) || (outBuff == null)) { + public void offerEncoder(byte[] frame, byte[] encodeBuff) { + if ((frame == null) || (encodeBuff == null)) { Log.e(TAG, "Offer Encoder input param invalid. "); return; } @@ -87,15 +87,15 @@ public class AvcEncoder { outBuf.get(configByte); } else if (bufferInfo.flags == MediaCodec.BUFFER_FLAG_KEY_FRAME) { outBuf.get(h264Buf); - System.arraycopy(configByte, 0, outBuff, 0, configByte.length); - System.arraycopy(h264Buf, 0, outBuff, configByte.length, h264Buf.length); + System.arraycopy(configByte, 0, encodeBuff, 0, configByte.length); + System.arraycopy(h264Buf, 0, encodeBuff, configByte.length, h264Buf.length); outBufLen = bufferInfo.size + configByte.length; - dataHandler.handleData(outBuff, outBufLen); + dataHandler.handleData(encodeBuff, outBufLen); } else { outBuf.get(h264Buf); - System.arraycopy(h264Buf, 0, outBuf, 0, h264Buf.length); + System.arraycopy(h264Buf, 0, encodeBuff, 0, h264Buf.length); outBufLen = bufferInfo.size; - dataHandler.handleData(outBuff, outBufLen); + dataHandler.handleData(encodeBuff, outBufLen); } mediaCodec.releaseOutputBuffer(outBufId, false); } else if (outBufId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/VirtualCamera.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/VirtualCamera.java index bef5f2b..6c0a52a 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/VirtualCamera.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/VirtualCamera.java @@ -151,17 +151,17 @@ public class VirtualCamera implements Camera.PreviewCallback { mCameraListener = listener; } - public void setResolution(short width, short height) { + public void setResolution(int width, int height) { mWidth = width == -1 ? DEFAULT_WIDTH : width; mHeight = height == -1 ? DEFAULT_HEIGHT : height; } - public short getWidth() { - return (short) mWidth; + public int getWidth() { + return mWidth; } - public short getHeight() { - return (short) mHeight; + public int getHeight() { + return mHeight; } public int getFacing() { diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/VirtualCameraManager.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/VirtualCameraManager.java index 9da13d2..cfbe546 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/VirtualCameraManager.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/VirtualCameraManager.java @@ -3,8 +3,15 @@ package com.huawei.cloudphone.virtualdevice.camera; import static com.huawei.cloudphone.jniwrapper.JNIWrapper.CAMERA_DATA; import static com.huawei.cloudphone.virtualdevice.common.VirtualDeviceProtocol.MSG_HEADER_LEN; +import android.Manifest; +import android.app.Activity; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Build; import android.util.Log; +import androidx.core.app.ActivityCompat; + import com.huawei.cloudphone.virtualdevice.common.IVirtualDeviceDataListener; import com.huawei.cloudphone.virtualdevice.common.VirtualDeviceManager; import com.huawei.cloudphone.virtualdevice.common.VirtualDeviceProtocol; @@ -34,13 +41,20 @@ public class VirtualCameraManager extends VirtualDeviceManager { private static final int RSP_INFO_LENGTH = 3; private static final int RSP_SIZE_LENGTH = 4; private static final int RSP_SUPPORT_H264_LENGTH = 1; + public static final String GRANT_CAMERA_PERMISSION_SUCCESS_ACTION = "android.intent.action.GRANT_CAMERA_PERMISSION_SUCCESS"; private VirtualCamera mVirtualCamera; private VirtualDeviceProtocol mVirtualDeviceProtocol; + private Context mContext; + private int mWidth; + private int mHeight; + private int mFrameRate; + private int mDevId; - public VirtualCameraManager(VirtualDeviceProtocol virtualDeviceProtocol) { + public VirtualCameraManager(VirtualDeviceProtocol virtualDeviceProtocol, Context context) { mVirtualCamera = new VirtualCamera(); mVirtualDeviceProtocol = virtualDeviceProtocol; + mContext = context; } public void stop() { @@ -77,6 +91,8 @@ public class VirtualCameraManager extends VirtualDeviceManager { Log.i(TAG, "processMsg: get size."); handleGetSizeReq(header, body); break; + default: + Log.e(TAG, "processMsg: Invalid msg type"); } } @@ -116,24 +132,33 @@ public class VirtualCameraManager extends VirtualDeviceManager { mVirtualDeviceProtocol.sendMsg(rspHeader, rspBody, CAMERA_DATA); } - private void handleStartPreviewReq(MsgHeader header, byte[] body) { - short width = (short)((body[0] << 8) | (body[1] & 0x00FF)); - short height = (short)((body[2] << 8) | (body[3] & 0x00FF)); - int fps = (body[4] << 8) | (body[5] & 0x00FF); + public void initCamera() { + if (mVirtualCamera.open(mDevId) != 0) { + Log.e(TAG, "initCamera: Failed to open camera"); + return; + } + mVirtualCamera.setOnRecvData(new CameraDataListener()); + mVirtualCamera.setResolution(mWidth, mHeight); + mVirtualCamera.setFps(mFrameRate); + if (mVirtualCamera.startPreview() != 0) { + Log.e(TAG, "initCamera: Failed to start preview"); + } + } - int result = mVirtualCamera.open(header.mDeviceId); - if (result == 0) { - mVirtualCamera.setOnRecvData(new CameraDataListener()); - mVirtualCamera.setResolution(width, height); - mVirtualCamera.setFps(fps); - result = mVirtualCamera.startPreview(); + private void handleStartPreviewReq(MsgHeader header, byte[] body) { + mWidth = (short)((body[0] << 8) | (body[1] & 0x00FF)); + mHeight = (short)((body[2] << 8) | (body[3] & 0x00FF)); + mFrameRate = (body[4] << 8) | (body[5] & 0x00FF); + mDevId = header.mDeviceId; + Context context = mContext.getApplicationContext(); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return; + if (context.checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions((Activity) mContext, + new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, + DEV_TYPE_CAMERA); + } else { + initCamera(); } - byte[] rspBody = new byte[RSP_RESULT_LENGTH]; - rspBody[0] = 0x0; - rspBody[1] = (byte) (result == 0 ? 0x0 : 0x1); - int rspMsgLen = MSG_HEADER_LEN + RSP_RESULT_LENGTH; - MsgHeader rspHeader = new MsgHeader(OPT_CAMERA_START_PREVIEW_RSP, DEV_TYPE_CAMERA, header.mDeviceId, rspMsgLen); - mVirtualDeviceProtocol.sendMsg(rspHeader, rspBody, CAMERA_DATA); } private void handleStopPreviewReq(MsgHeader header, byte[] body) { @@ -155,8 +180,8 @@ public class VirtualCameraManager extends VirtualDeviceManager { } private void handleGetSizeReq(MsgHeader header, byte[] body) { - int width = mVirtualCamera.getWidth(); - int height = mVirtualCamera.getHeight(); + int width = -1; + int height = -1; byte[] rspBody = new byte[RSP_RESULT_LENGTH + RSP_SIZE_LENGTH]; rspBody[0] = 0x0; diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/VirtualDeviceProtocol.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/VirtualDeviceProtocol.java index ffcf03b..70b1d31 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/VirtualDeviceProtocol.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/VirtualDeviceProtocol.java @@ -1,9 +1,15 @@ package com.huawei.cloudphone.virtualdevice.common; import static com.huawei.cloudphone.virtualdevice.camera.VirtualCameraManager.DEV_TYPE_CAMERA; +import static com.huawei.cloudphone.virtualdevice.camera.VirtualCameraManager.GRANT_CAMERA_PERMISSION_SUCCESS_ACTION; import static com.huawei.cloudphone.virtualdevice.common.VirtualDeviceManager.DEV_TYPE_MICROPHONE; import static com.huawei.cloudphone.virtualdevice.common.VirtualDeviceManager.DEV_TYPE_SENSOR; +import static com.huawei.cloudphone.virtualdevice.microphone.VirtualMicrophoneManager.GRANT_MICROPHONE_PERMISSION_SUCCESS_ACTION; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.hardware.SensorManager; import android.util.Log; @@ -23,17 +29,35 @@ public class VirtualDeviceProtocol { private PacketParseThread mPktProcessThread; static Object mSendMsgLock = new Object(); private Map virtualDeviceManagers; + private Context mContext; + private BroadcastReceiver mPermissionResultReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (GRANT_CAMERA_PERMISSION_SUCCESS_ACTION.equals(intent.getAction())) { + ((VirtualCameraManager)virtualDeviceManagers.get(DEV_TYPE_CAMERA)).initCamera(); + } else if (GRANT_MICROPHONE_PERMISSION_SUCCESS_ACTION.equals(intent.getAction())) { + ((VirtualMicrophoneManager)virtualDeviceManagers.get(DEV_TYPE_MICROPHONE)).initMicrophone(); + } + } + }; - public VirtualDeviceProtocol(IVirtualDeviceIO virtualDeviceIO ,SensorManager sensorManager) { + public VirtualDeviceProtocol(IVirtualDeviceIO virtualDeviceIO ,Context context) { mVirtualDeviceIO = virtualDeviceIO; - initVirtualDeviceManagers(sensorManager); + mContext = context; + initVirtualDeviceManagers(); } - private void initVirtualDeviceManagers(SensorManager sensorManager) { + private void initVirtualDeviceManagers() { virtualDeviceManagers = new HashMap(); - virtualDeviceManagers.put(DEV_TYPE_CAMERA, new VirtualCameraManager(this)); - virtualDeviceManagers.put(DEV_TYPE_MICROPHONE, new VirtualMicrophoneManager(this)); - virtualDeviceManagers.put(DEV_TYPE_SENSOR, new VirtualSensorManager(this, sensorManager)); + virtualDeviceManagers.put(DEV_TYPE_CAMERA, new VirtualCameraManager(this, mContext)); + virtualDeviceManagers.put(DEV_TYPE_MICROPHONE, new VirtualMicrophoneManager(this, mContext)); + virtualDeviceManagers.put(DEV_TYPE_SENSOR, new VirtualSensorManager(this, + (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE))); + + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(GRANT_CAMERA_PERMISSION_SUCCESS_ACTION); + intentFilter.addAction(GRANT_MICROPHONE_PERMISSION_SUCCESS_ACTION); + mContext.registerReceiver(mPermissionResultReceiver, intentFilter); } public void processMsg(MsgHeader header, byte[] body) { @@ -53,6 +77,7 @@ public class VirtualDeviceProtocol { for (Map.Entry entry : virtualDeviceManagers.entrySet()) { entry.getValue().start(); } + mContext.unregisterReceiver(mPermissionResultReceiver); } public void stopProcess() { @@ -99,7 +124,7 @@ public class VirtualDeviceProtocol { return readLen; } - private synchronized int writeN(byte[] data, int offset, int len, int deviceType) { + private int writeN(byte[] data, int offset, int len, int deviceType) { int writeLen = 0; while(len > 0) { if(!mIsTaskRun) { @@ -167,15 +192,15 @@ public class VirtualDeviceProtocol { mData[3] = (byte) (optType & 0x00FF); mData[4] = (byte) (devType >> 8); - mData[5] = (byte) (devType & 0x00FF); + mData[5] = (byte) (devType & 0xFF); mData[6] = (byte) (devId >> 8); - mData[7] = (byte) (devId & 0x00FF); + mData[7] = (byte) (devId & 0xFF); mData[8] = (byte) ((msgLen & 0xFF000000) >> 24); - mData[9] = (byte) ((msgLen & 0xFF000000) >> 16); - mData[10] = (byte) ((msgLen & 0xFF000000) >> 8); - mData[11] = (byte) (msgLen & 0xFF000000); + mData[9] = (byte) ((msgLen & 0x00FF0000) >> 16); + mData[10] = (byte) ((msgLen & 0x0000FF00) >> 8); + mData[11] = (byte) (msgLen & 0x000000FF); mData[12] = 0x00; mData[13] = 0x00; diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/microphone/VirtualMicrophoneManager.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/microphone/VirtualMicrophoneManager.java index 4eec2c3..532f95e 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/microphone/VirtualMicrophoneManager.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/microphone/VirtualMicrophoneManager.java @@ -9,8 +9,15 @@ import static com.huawei.cloudphone.virtualdevice.microphone.VirtualMicrophone.F import static com.huawei.cloudphone.virtualdevice.microphone.VirtualMicrophone.MAX_FRAME_SIZE; import static com.huawei.cloudphone.virtualdevice.microphone.VirtualMicrophone.SAMPLE_RATE; +import android.Manifest; +import android.app.Activity; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Build; import android.util.Log; +import androidx.core.app.ActivityCompat; + import com.huawei.cloudphone.virtualdevice.common.IVirtualDeviceDataListener; import com.huawei.cloudphone.virtualdevice.common.VirtualDeviceManager; import com.huawei.cloudphone.virtualdevice.common.VirtualDeviceProtocol; @@ -33,13 +40,16 @@ public class VirtualMicrophoneManager extends VirtualDeviceManager { private static final int RSP_RESULT_LENGTH = 2; private static final int RSP_AUDIO_TYPE_LENGTH = 2; + public static final String GRANT_MICROPHONE_PERMISSION_SUCCESS_ACTION = "android.intent.action.GRANT_MICROPHONE_PERMISSION_SUCCESS"; private VirtualMicrophone mVirtualMicrophone; private VirtualDeviceProtocol mVirtualDeviceProtocol; + private Context mContext; - public VirtualMicrophoneManager(VirtualDeviceProtocol virtualDeviceProtocol) { + public VirtualMicrophoneManager(VirtualDeviceProtocol virtualDeviceProtocol, Context context) { mVirtualDeviceProtocol = virtualDeviceProtocol; mVirtualMicrophone = new VirtualMicrophone(); + mContext = context; } public void processMsg(MsgHeader header, byte[] msgBody) { @@ -59,17 +69,23 @@ public class VirtualMicrophoneManager extends VirtualDeviceManager { } } - private void handleStartRecordReq(byte[] msgBody) { + public void initMicrophone() { mVirtualMicrophone.setOnRecvDataListener(new MicrophoneDataListener()); - int result = mVirtualMicrophone.start(); - byte[] rspBody = new byte[RSP_RESULT_LENGTH + RSP_AUDIO_TYPE_LENGTH]; - rspBody[0] = 0x0; - rspBody[1] = (byte) (result == 0 ? 0x0 : 0x1); - rspBody[2] = 0x00; - rspBody[3] = 0x01; - int rspMsgLen = MSG_HEADER_LEN + RSP_RESULT_LENGTH + RSP_AUDIO_TYPE_LENGTH; - MsgHeader rspHeader = new MsgHeader(OPT_MICROPHONE_START_RECORD_RSP, DEV_TYPE_MICROPHONE, DEV_MIC_ID, rspMsgLen); - mVirtualDeviceProtocol.sendMsg(rspHeader, rspBody, MICROPHONE_DATA); + if (mVirtualMicrophone.start() != 0) { + Log.e(TAG, "initMicrophone: failed to start microphone"); + } + } + + private void handleStartRecordReq(byte[] msgBody) { + Context context = mContext.getApplicationContext(); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return; + if (context.checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions((Activity) mContext, + new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE}, + DEV_TYPE_MICROPHONE); + } else { + initMicrophone(); + } } private void handleStopRecordReq(byte[] msgBody) { diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/sensor/VirtualSensorManager.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/sensor/VirtualSensorManager.java index ef04cec..eb41786 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/sensor/VirtualSensorManager.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/sensor/VirtualSensorManager.java @@ -68,12 +68,12 @@ public class VirtualSensorManager extends VirtualDeviceManager { String xStr = Float.valueOf((float) args[0]).toString(); String yStr = Float.valueOf((float) args[1]).toString(); String zStr = Float.valueOf((float) args[2]).toString(); - String accurateStr = Integer.valueOf((int) args[4]).toString(); + String accurateStr = Integer.valueOf((int) args[3]).toString(); String body = xStr + ":" + yStr + ":" + zStr + ":" + accurateStr + ":"; - short type = (short) args[4]; + int type = (int) args[4]; int bodyLen = body.getBytes().length; int rspMsgLen = bodyLen + MSG_HEADER_LEN; - MsgHeader header = new MsgHeader(OPT_SENSOR_DATA, DEV_TYPE_SENSOR, type, rspMsgLen); + MsgHeader header = new MsgHeader(OPT_SENSOR_DATA, DEV_TYPE_SENSOR, (short)type, rspMsgLen); byte[] rspBody = new byte[bodyLen]; System.arraycopy(body.getBytes(), 0, rspBody, 0, bodyLen); mVirtualDeviceProtocol.sendMsg(header, rspBody, SENSOR_DATA); -- Gitee From e8d396ff5730a59fb72ff6cb1dcd6b0390d99ce4 Mon Sep 17 00:00:00 2001 From: wangshuo <584363327@qq.com> Date: Tue, 30 May 2023 14:41:35 +0800 Subject: [PATCH 3/9] =?UTF-8?q?android=20sdk=E6=94=AF=E6=8C=81=E8=99=9A?= =?UTF-8?q?=E6=8B=9F=E4=BB=BF=E7=9C=9F=E8=AE=BE=E5=A4=87-=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0copyright?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/CloudPhoneVirtualDevDataListener.java | 15 +++++++++++++++ .../virtualdevice/VirtualDeviceSession.java | 15 +++++++++++++++ .../virtualdevice/camera/AvcEncoder.java | 15 +++++++++++++++ .../camera/AvcEncoderDataHandler.java | 15 +++++++++++++++ .../virtualdevice/camera/VirtualCamera.java | 15 +++++++++++++++ .../camera/VirtualCameraManager.java | 15 +++++++++++++++ .../common/IVirtualDeviceDataListener.java | 15 +++++++++++++++ .../virtualdevice/common/IVirtualDeviceIO.java | 15 +++++++++++++++ .../virtualdevice/common/ParamBundle.java | 15 +++++++++++++++ .../virtualdevice/common/RingBuffer.java | 15 +++++++++++++++ .../common/RingBufferVirtualDeviceIO.java | 18 +++++++++++++++--- .../common/VirtualDeviceManager.java | 15 +++++++++++++++ .../common/VirtualDeviceProtocol.java | 15 +++++++++++++++ .../microphone/VirtualMicrophone.java | 15 +++++++++++++++ .../microphone/VirtualMicrophoneManager.java | 17 +++++++++++++++-- .../virtualdevice/sensor/VirtualSensor.java | 15 +++++++++++++++ .../sensor/VirtualSensorManager.java | 15 +++++++++++++++ 17 files changed, 255 insertions(+), 5 deletions(-) diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/api/CloudPhoneVirtualDevDataListener.java b/cloudphone/src/main/java/com/huawei/cloudphone/api/CloudPhoneVirtualDevDataListener.java index 87a2119..abee14c 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/api/CloudPhoneVirtualDevDataListener.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/api/CloudPhoneVirtualDevDataListener.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023 Huawei Cloud Computing Technology Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.huawei.cloudphone.api; public interface CloudPhoneVirtualDevDataListener { diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/VirtualDeviceSession.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/VirtualDeviceSession.java index 1f4c464..a4ff95e 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/VirtualDeviceSession.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/VirtualDeviceSession.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023 Huawei Cloud Computing Technology Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.huawei.cloudphone.virtualdevice; import android.content.Context; diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/AvcEncoder.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/AvcEncoder.java index bd51c22..f488194 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/AvcEncoder.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/AvcEncoder.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023 Huawei Cloud Computing Technology Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.huawei.cloudphone.virtualdevice.camera; import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar; diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/AvcEncoderDataHandler.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/AvcEncoderDataHandler.java index 3cb01b8..7036833 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/AvcEncoderDataHandler.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/AvcEncoderDataHandler.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023 Huawei Cloud Computing Technology Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.huawei.cloudphone.virtualdevice.camera; public interface AvcEncoderDataHandler { diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/VirtualCamera.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/VirtualCamera.java index 6c0a52a..c033b7d 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/VirtualCamera.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/VirtualCamera.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023 Huawei Cloud Computing Technology Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.huawei.cloudphone.virtualdevice.camera; import static android.hardware.Camera.Parameters.FOCUS_MODE_AUTO; diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/VirtualCameraManager.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/VirtualCameraManager.java index cfbe546..00317b5 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/VirtualCameraManager.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/camera/VirtualCameraManager.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023 Huawei Cloud Computing Technology Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.huawei.cloudphone.virtualdevice.camera; import static com.huawei.cloudphone.jniwrapper.JNIWrapper.CAMERA_DATA; diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/IVirtualDeviceDataListener.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/IVirtualDeviceDataListener.java index 263f633..c7d0f5e 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/IVirtualDeviceDataListener.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/IVirtualDeviceDataListener.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023 Huawei Cloud Computing Technology Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.huawei.cloudphone.virtualdevice.common; public interface IVirtualDeviceDataListener { diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/IVirtualDeviceIO.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/IVirtualDeviceIO.java index 7880332..2bb0b98 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/IVirtualDeviceIO.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/IVirtualDeviceIO.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023 Huawei Cloud Computing Technology Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.huawei.cloudphone.virtualdevice.common; public interface IVirtualDeviceIO { diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/ParamBundle.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/ParamBundle.java index c43342f..d244a7e 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/ParamBundle.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/ParamBundle.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023 Huawei Cloud Computing Technology Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.huawei.cloudphone.virtualdevice.common; import android.content.Context; diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/RingBuffer.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/RingBuffer.java index ab087dd..97e9e8a 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/RingBuffer.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/RingBuffer.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023 Huawei Cloud Computing Technology Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.huawei.cloudphone.virtualdevice.common; import java.util.Arrays; diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/RingBufferVirtualDeviceIO.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/RingBufferVirtualDeviceIO.java index faf8879..4a8f7ed 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/RingBufferVirtualDeviceIO.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/RingBufferVirtualDeviceIO.java @@ -1,8 +1,20 @@ +/* + * Copyright 2023 Huawei Cloud Computing Technology Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.huawei.cloudphone.virtualdevice.common; -import static com.huawei.cloudphone.jniwrapper.JNIWrapper.CAMERA_DATA; -import static com.huawei.cloudphone.jniwrapper.JNIWrapper.MICROPHONE_DATA; - import com.huawei.cloudphone.api.CloudPhoneManager; import java.nio.ByteBuffer; diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/VirtualDeviceManager.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/VirtualDeviceManager.java index 45d7e33..48d8d5e 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/VirtualDeviceManager.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/VirtualDeviceManager.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023 Huawei Cloud Computing Technology Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.huawei.cloudphone.virtualdevice.common; import com.huawei.cloudphone.virtualdevice.common.VirtualDeviceProtocol.MsgHeader; diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/VirtualDeviceProtocol.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/VirtualDeviceProtocol.java index 70b1d31..9e3983e 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/VirtualDeviceProtocol.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/common/VirtualDeviceProtocol.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023 Huawei Cloud Computing Technology Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.huawei.cloudphone.virtualdevice.common; import static com.huawei.cloudphone.virtualdevice.camera.VirtualCameraManager.DEV_TYPE_CAMERA; diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/microphone/VirtualMicrophone.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/microphone/VirtualMicrophone.java index bf41dea..fca6edf 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/microphone/VirtualMicrophone.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/microphone/VirtualMicrophone.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023 Huawei Cloud Computing Technology Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.huawei.cloudphone.virtualdevice.microphone; import android.media.AudioFormat; diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/microphone/VirtualMicrophoneManager.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/microphone/VirtualMicrophoneManager.java index 532f95e..b80a81b 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/microphone/VirtualMicrophoneManager.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/microphone/VirtualMicrophoneManager.java @@ -1,10 +1,23 @@ +/* + * Copyright 2023 Huawei Cloud Computing Technology Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.huawei.cloudphone.virtualdevice.microphone; -import static com.huawei.cloudphone.jniwrapper.JNIWrapper.CAMERA_DATA; import static com.huawei.cloudphone.jniwrapper.JNIWrapper.MICROPHONE_DATA; import static com.huawei.cloudphone.virtualdevice.common.VirtualDeviceProtocol.MSG_HEADER_LEN; import static com.huawei.cloudphone.virtualdevice.microphone.VirtualMicrophone.CHANNEL; -import static com.huawei.cloudphone.virtualdevice.microphone.VirtualMicrophone.DEFAULT_SAMPLE_RATE; import static com.huawei.cloudphone.virtualdevice.microphone.VirtualMicrophone.FORMAT; import static com.huawei.cloudphone.virtualdevice.microphone.VirtualMicrophone.MAX_FRAME_SIZE; import static com.huawei.cloudphone.virtualdevice.microphone.VirtualMicrophone.SAMPLE_RATE; diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/sensor/VirtualSensor.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/sensor/VirtualSensor.java index 452cfbb..9850e19 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/sensor/VirtualSensor.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/sensor/VirtualSensor.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023 Huawei Cloud Computing Technology Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.huawei.cloudphone.virtualdevice.sensor; import android.hardware.Sensor; diff --git a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/sensor/VirtualSensorManager.java b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/sensor/VirtualSensorManager.java index eb41786..c363a67 100644 --- a/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/sensor/VirtualSensorManager.java +++ b/cloudphone/src/main/java/com/huawei/cloudphone/virtualdevice/sensor/VirtualSensorManager.java @@ -1,3 +1,18 @@ +/* + * Copyright 2023 Huawei Cloud Computing Technology Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.huawei.cloudphone.virtualdevice.sensor; import static com.huawei.cloudphone.jniwrapper.JNIWrapper.SENSOR_DATA; -- Gitee From 78a51e6a3390860a719977fc069717dce590c45b Mon Sep 17 00:00:00 2001 From: wangshuo <584363327@qq.com> Date: Thu, 1 Jun 2023 22:20:18 +0800 Subject: [PATCH 4/9] =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E9=85=8D=E7=BD=AE=EF=BC=8C=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96=E9=9F=B3=E8=A7=86=E9=A2=91=E6=97=B6=E5=8F=91=E9=80=81?= =?UTF-8?q?=E5=8F=82=E6=95=B0=E7=BB=99=E4=BA=91=E6=9C=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cloudapp/common/CasConnectInfo.java | 72 +++++++++ .../cloudapp/ui/CasCloudMainActivity.java | 71 ++++++++- .../cloudapp/ui/CasCloudPhoneActivity.java | 8 + .../cloudapp/utils/CasConstantsUtil.java | 20 +++ .../res/layout/activity_cas_cloud_main.xml | 146 +++++++++++++++++- app/src/main/res/values/strings.xml | 9 ++ .../cloudphone/apiimpl/CloudPhoneImpl.java | 15 ++ .../cloudphone/common/CasConnectorInfo.java | 71 +++++++++ .../cloudphone/utils/CasConstantsUtil.java | 20 +++ 9 files changed, 426 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/huawei/cloudapp/common/CasConnectInfo.java b/app/src/main/java/com/huawei/cloudapp/common/CasConnectInfo.java index 555a99c..54862f2 100644 --- a/app/src/main/java/com/huawei/cloudapp/common/CasConnectInfo.java +++ b/app/src/main/java/com/huawei/cloudapp/common/CasConnectInfo.java @@ -26,10 +26,14 @@ import static com.huawei.cloudapp.utils.CasConstantsUtil.AES_KEY_PATTERN; import static com.huawei.cloudapp.utils.CasConstantsUtil.AUTH_TS; import static com.huawei.cloudapp.utils.CasConstantsUtil.AVAILABLE_PLAYTIME; import static com.huawei.cloudapp.utils.CasConstantsUtil.BACKGROUND_TIMEOUT; +import static com.huawei.cloudapp.utils.CasConstantsUtil.ENCODE_TYPE; +import static com.huawei.cloudapp.utils.CasConstantsUtil.FRAME_TYPE; import static com.huawei.cloudapp.utils.CasConstantsUtil.IP; import static com.huawei.cloudapp.utils.CasConstantsUtil.IP_PATTERN; import static com.huawei.cloudapp.utils.CasConstantsUtil.PORT; import static com.huawei.cloudapp.utils.CasConstantsUtil.POSITIVE_NUMBER_PATTERN; +import static com.huawei.cloudapp.utils.CasConstantsUtil.REMOTE_ENCODE_SERVER_IP; +import static com.huawei.cloudapp.utils.CasConstantsUtil.REMOTE_ENCODE_SERVER_PORT; import static com.huawei.cloudapp.utils.CasConstantsUtil.SESSION_ID; import static com.huawei.cloudapp.utils.CasConstantsUtil.TICKET; import static com.huawei.cloudapp.utils.CasConstantsUtil.TOUCH_TIMEOUT; @@ -69,6 +73,26 @@ public class CasConnectInfo implements Parcelable { */ private String connectPort; + /** + * 远端编码服务器ip + */ + private String remoteEncodeServerIp; + + /** + * 远端编码服务器端口 + */ + private String remoteEncodeServerPort; + + /** + * 编码类型 + */ + private int encodeType; + + /** + * 视频帧类型 + */ + private String frameType; + /** * 连接sessionId */ @@ -145,6 +169,10 @@ public class CasConnectInfo implements Parcelable { parcel.writeString(aesKey); parcel.writeString(userID); parcel.writeString(touchTimeout); + parcel.writeString(frameType); + parcel.writeInt(encodeType); + parcel.writeString(remoteEncodeServerIp); + parcel.writeString(remoteEncodeServerPort); } public String getTicket() { @@ -227,6 +255,38 @@ public class CasConnectInfo implements Parcelable { touchTimeout = timeout; } + public String getRemoteEncodeServerIp() { + return remoteEncodeServerIp; + } + + public void setRemoteEncodeServerIp(String remoteEncodeServerIp) { + this.remoteEncodeServerIp = remoteEncodeServerIp; + } + + public String getRemoteEncodeServerPort() { + return remoteEncodeServerPort; + } + + public void setRemoteEncodeServerPort(String remoteEncodeServerPort) { + this.remoteEncodeServerPort = remoteEncodeServerPort; + } + + public int getEncodeType() { + return encodeType; + } + + public void setEncodeType(int encodeType) { + this.encodeType = encodeType; + } + + public String getFrameType() { + return frameType; + } + + public void setFrameType(String frameType) { + this.frameType = frameType; + } + /** * check start params *

check params

@@ -308,6 +368,14 @@ public class CasConnectInfo implements Parcelable { return false; } } + String frameType = params.get(FRAME_TYPE); + if (frameType == null) { + frameType = "h264"; + } + String encodeType = params.get(ENCODE_TYPE); + if (encodeType == null) { + encodeType = "0"; + } setConnectIp(ip); setConnectPort(port); setBackgroundTimeout(backgroundTimeout); @@ -318,6 +386,10 @@ public class CasConnectInfo implements Parcelable { setAesKey(aesKey); setUserId(userId); setTouchTimeout(touchTimeOut); + setFrameType(frameType); + setEncodeType(Integer.parseInt(encodeType)); + setRemoteEncodeServerIp(params.get(REMOTE_ENCODE_SERVER_IP)); + setRemoteEncodeServerIp(params.get(REMOTE_ENCODE_SERVER_PORT)); return true; } } diff --git a/app/src/main/java/com/huawei/cloudapp/ui/CasCloudMainActivity.java b/app/src/main/java/com/huawei/cloudapp/ui/CasCloudMainActivity.java index 4501f59..b205670 100644 --- a/app/src/main/java/com/huawei/cloudapp/ui/CasCloudMainActivity.java +++ b/app/src/main/java/com/huawei/cloudapp/ui/CasCloudMainActivity.java @@ -22,6 +22,8 @@ import android.os.Bundle; import android.text.TextUtils; import android.view.View; import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.RadioGroup; import com.huawei.cloudapp.R; import com.huawei.cloudapp.common.CASLog; @@ -34,6 +36,14 @@ public class CasCloudMainActivity extends Activity { private static final String TAG = "CasCloudMainActivity"; private EditText mCloudPhoneId; private EditText mCloudPhonePort; + private RadioGroup mEncodeTypeGroup; + private int mEncodeType = 0; + private String mFrameType = "h264"; + private LinearLayout mRemoteEncodeServerIpLayout; + private LinearLayout mRemoteEncodeServerPortLayout; + private EditText mRemoteEncodeServerIp; + private EditText mRemoteEncodeServerPort; + private RadioGroup mFrameTypeGroup; @Override protected void onCreate(Bundle savedInstanceState) { @@ -45,6 +55,52 @@ public class CasCloudMainActivity extends Activity { private void initView() { mCloudPhoneId = findViewById(R.id.cloud_phone_id); mCloudPhonePort = findViewById(R.id.cloud_phone_port); + mEncodeTypeGroup = findViewById(R.id.encodeTypeRadioButtonGroup); + mFrameTypeGroup = findViewById(R.id.frameTypeRadioButtonGroup); + mRemoteEncodeServerIpLayout = findViewById(R.id.remote_ip_view_layout); + mRemoteEncodeServerPortLayout = findViewById(R.id.remote_port_view_layout); + mRemoteEncodeServerIp = findViewById(R.id.edit_text_remote_ip); + mRemoteEncodeServerPort = findViewById(R.id.edit_text_remote_port); + + mEncodeTypeGroup.check(R.id.radioButtonCpu); + mEncodeTypeGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(RadioGroup radioGroup, int i) { + switch (i) { + case R.id.radioButtonCpu: + mEncodeType = 1; + mRemoteEncodeServerIpLayout.setVisibility(View.GONE); + mRemoteEncodeServerPortLayout.setVisibility(View.GONE); + break; + case R.id.radioButtonGpu: + mEncodeType = 2; + mRemoteEncodeServerIpLayout.setVisibility(View.GONE); + mRemoteEncodeServerPortLayout.setVisibility(View.GONE); + break; + case R.id.radioButtonRemote: + mRemoteEncodeServerIpLayout.setVisibility(View.VISIBLE); + mRemoteEncodeServerPortLayout.setVisibility(View.VISIBLE); + break; + default: + mEncodeType = 0; + mRemoteEncodeServerIpLayout.setVisibility(View.GONE); + mRemoteEncodeServerPortLayout.setVisibility(View.GONE); + break; + } + } + }); + + mFrameTypeGroup.check(R.id.radioButtonH264); + mEncodeTypeGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(RadioGroup radioGroup, int i) { + if (i == R.id.radioButtonH265) { + mFrameType = "h265"; + } else { + mFrameType = "h264"; + } + } + }); } public void cloudPhoneConnect(View view) { @@ -58,18 +114,21 @@ public class CasCloudMainActivity extends Activity { CASLog.e(TAG, "输入Port为空"); return; } - startCloudPhoneActivity(phoneIp, phonePort); + String remoteEncodeServerIP = mRemoteEncodeServerIp.getText().toString().trim(); + String remoteEncodeServerPort = mRemoteEncodeServerPort.getText().toString().trim(); + + startCloudPhoneActivity(phoneIp, phonePort, remoteEncodeServerIP, remoteEncodeServerPort); } - private void startCloudPhoneActivity(String phoneIp, String phonePort) { + private void startCloudPhoneActivity(String phoneIp, String phonePort, String remoteEncodeServerIP, String remoteEncodeServerPort) { Intent intent = new Intent(CasCloudMainActivity.this, CasCloudPhoneActivity.class); - CasConnectInfo connectorInfo = getCasConnectorInfo(phoneIp, phonePort); + CasConnectInfo connectorInfo = getCasConnectorInfo(phoneIp, phonePort, remoteEncodeServerIP, remoteEncodeServerPort); intent.putExtra(CasConnectInfo.BUNDLE_KEY, connectorInfo); startActivity(intent); } - private CasConnectInfo getCasConnectorInfo(String phoneIp, String phonePort) { + private CasConnectInfo getCasConnectorInfo(String phoneIp, String phonePort, String remoteEncodeServerIP, String remoteEncodeServerPort) { String sessionId = UUID.randomUUID().toString().replaceAll("-", ""); CasConnectInfo connectorInfo = new CasConnectInfo(); connectorInfo.setConnectIp(phoneIp); @@ -82,6 +141,10 @@ public class CasCloudMainActivity extends Activity { connectorInfo.setAesKey("11111111111111111111111111111111"); connectorInfo.setAuthTs("987654321"); connectorInfo.setTouchTimeout("0"); + connectorInfo.setEncodeType(mEncodeType); + connectorInfo.setFrameType(mFrameType); + connectorInfo.setRemoteEncodeServerIp(remoteEncodeServerIP); + connectorInfo.setRemoteEncodeServerPort(remoteEncodeServerPort); return connectorInfo; } diff --git a/app/src/main/java/com/huawei/cloudapp/ui/CasCloudPhoneActivity.java b/app/src/main/java/com/huawei/cloudapp/ui/CasCloudPhoneActivity.java index cd098ab..a26c665 100644 --- a/app/src/main/java/com/huawei/cloudapp/ui/CasCloudPhoneActivity.java +++ b/app/src/main/java/com/huawei/cloudapp/ui/CasCloudPhoneActivity.java @@ -79,8 +79,12 @@ import static com.huawei.cloudapp.utils.CasConstantsUtil.AES_KEY; import static com.huawei.cloudapp.utils.CasConstantsUtil.AUTH_TS; import static com.huawei.cloudapp.utils.CasConstantsUtil.AVAILABLE_PLAYTIME; import static com.huawei.cloudapp.utils.CasConstantsUtil.BACKGROUND_TIMEOUT; +import static com.huawei.cloudapp.utils.CasConstantsUtil.ENCODE_TYPE; +import static com.huawei.cloudapp.utils.CasConstantsUtil.FRAME_TYPE; import static com.huawei.cloudapp.utils.CasConstantsUtil.IP; import static com.huawei.cloudapp.utils.CasConstantsUtil.PORT; +import static com.huawei.cloudapp.utils.CasConstantsUtil.REMOTE_ENCODE_SERVER_IP; +import static com.huawei.cloudapp.utils.CasConstantsUtil.REMOTE_ENCODE_SERVER_PORT; import static com.huawei.cloudapp.utils.CasConstantsUtil.SESSION_ID; import static com.huawei.cloudapp.utils.CasConstantsUtil.TICKET; import static com.huawei.cloudapp.utils.CasConstantsUtil.TOUCH_TIMEOUT; @@ -294,6 +298,10 @@ public class CasCloudPhoneActivity extends FragmentActivity implements View.OnCl parasMap.put(AVAILABLE_PLAYTIME, connectInfo.getAvailablePlayTime()); parasMap.put(USER_ID, connectInfo.getUserId()); parasMap.put(TOUCH_TIMEOUT, connectInfo.getTouchTimeout()); + parasMap.put(FRAME_TYPE, connectInfo.getFrameType()); + parasMap.put(ENCODE_TYPE, String.valueOf(connectInfo.getEncodeType())); + parasMap.put(REMOTE_ENCODE_SERVER_IP, connectInfo.getRemoteEncodeServerIp()); + parasMap.put(REMOTE_ENCODE_SERVER_PORT, connectInfo.getRemoteEncodeServerPort()); mCloudPhone.registerCloudPhoneStateListener(new CloudPhoneStateListenerImpl()); mCloudPhone.registerOnOrientationChangeListener(new CloudPhoneOrientationListener()); mCloudPhone.registerOnVirtualDevDataListener(new CloudPhoneVirtualDevDataListenerImpl()); diff --git a/app/src/main/java/com/huawei/cloudapp/utils/CasConstantsUtil.java b/app/src/main/java/com/huawei/cloudapp/utils/CasConstantsUtil.java index 9b7fb51..fcba689 100644 --- a/app/src/main/java/com/huawei/cloudapp/utils/CasConstantsUtil.java +++ b/app/src/main/java/com/huawei/cloudapp/utils/CasConstantsUtil.java @@ -83,6 +83,26 @@ public class CasConstantsUtil { */ public static final String TOUCH_TIMEOUT = "touch_timeout"; + /** + * frame_type + */ + public static final String FRAME_TYPE = "frame_type"; + + /** + * encode_type + */ + public static final String ENCODE_TYPE = "encode_type"; + + /** + * remote_encode_server_ip + */ + public static final String REMOTE_ENCODE_SERVER_IP = "remote_encode_server_ip"; + + /** + * remote_encode_server_port + */ + public static final String REMOTE_ENCODE_SERVER_PORT = "remote_encode_server_port"; + /** * check aes key */ diff --git a/app/src/main/res/layout/activity_cas_cloud_main.xml b/app/src/main/res/layout/activity_cas_cloud_main.xml index e66ff8c..b2e7e16 100644 --- a/app/src/main/res/layout/activity_cas_cloud_main.xml +++ b/app/src/main/res/layout/activity_cas_cloud_main.xml @@ -12,7 +12,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="20dp" - android:layout_marginTop="50dp" + android:layout_marginTop="30dp" android:layout_marginRight="20dp" android:orientation="horizontal"> @@ -39,7 +39,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="20dp" - android:layout_marginTop="50dp" + android:layout_marginTop="30dp" android:layout_marginRight="20dp" android:orientation="horizontal"> @@ -62,6 +62,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +