AndroidからPCへタップ座標値をUDPで送信

■プログラムの概要
　・Android側：画面アイコンをタップ移動した際の座標をUDP送信。
　・PC側：winapi（言語：C++）ベースで作成したUDP受信処理。
　・開発環境：AndroidStudioBumblebee2021.1.1　　VisualSutudio2013
　・送信確認ツール：Wireshark ver3.6.3
　・用途：電子楽器。※YouTube動画リンク先：**https://youtu.be/9AdsV2z7p_E**
　・備考：UDP受信後の処理（音生成／音色制御）は割愛してます。

■Android側のプログラムのソースコード
　（補足）touch_iconはトランペットアイコン、bgは背景画像。いずれもpptで作成。
```java:activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#fed"
    tools:context="com.example.Glitter_20220504.MainActivity">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:contentDescription="@string/img_description1"
        android:scaleType="centerCrop"
        android:src="@drawable/bg" />

    <TextView
           android:id="@+id/text_view_apt"
           android:layout_width="200dp"
           android:layout_height="wrap_content"
           android:layout_marginTop="260dp"
           android:layout_marginLeft="350dp"
           android:textSize="50sp"
           app:layout_constraintDimensionRatio="w,1:1"
           app:layout_constraintEnd_toEndOf="parent"
           app:layout_constraintStart_toStartOf="parent"
           app:layout_constraintTop_toTopOf="parent" />

       <TextView
           android:id="@+id/text_view_cav"
           android:layout_width="200dp"
           android:layout_height="wrap_content"
           android:layout_marginTop="10dp"
           android:layout_marginLeft="60dp"
           android:textSize="50sp"
           app:layout_constraintDimensionRatio="w,1:1"
           app:layout_constraintEnd_toEndOf="parent"
           app:layout_constraintStart_toStartOf="parent"
           app:layout_constraintTop_toTopOf="parent" />

    <com.example.Glitter_20220504.CustomImageView
        android:id="@+id/image_view"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_marginLeft="100dp"
        android:layout_marginTop="100dp"
        android:contentDescription="@string/description"
        android:src="@drawable/touch_icon"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/text_view" />
</RelativeLayout>
```

```java:MainActivity.java
//参照サイト：https://www.wabiapp.com/WabiSampleSource/windows/udp_server.html

package com.example.Glitter_20220504;

import androidx.appcompat.app.AppCompatActivity;
import android.content.pm.ActivityInfo;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class MainActivity extends AppCompatActivity
        implements View.OnTouchListener {

    private  CustomImageView cImageView;
    private int preDx, preDy;
    private TextView textView_apt;
    private TextView textView_cav;
    //パソコンに送信する値
    int posi_apt;
    int posi_cav;

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

        //横画面に固定する
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

        textView_apt = findViewById(R.id.text_view_apt);
        textView_cav = findViewById(R.id.text_view_cav);
        cImageView = this.findViewById(R.id.image_view);
        cImageView.setOnTouchListener(this);
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        // x,y 位置取得
        int newDx = (int)event.getRawX();
        int newDy = (int)event.getRawY();
        switch (event.getAction()) {
            // タッチダウンでdragされた
            case MotionEvent.ACTION_MOVE:
                // ACTION_MOVEでの位置
                // performCheckを入れろと警告が出るので
                v.performClick();
                int dx = cImageView.getLeft() + (newDx - preDx);
                int dy = cImageView.getTop() + (newDy - preDy);
                int max_x=1500; int max_y=800;
                if(dx >= max_x) { dx = max_x; } if(dx <= 0) { dx = 0; }
                if(dy >= max_y) { dy = max_y; } if(dy <= 0) { dy = 0; }
                int imgW = dx + cImageView.getWidth();
                int imgH = dy + cImageView.getHeight();
                //座標をudp送信処理に渡す
                posi_apt = (int)(((float)dx/(float)max_x)*(-50)+100);//100~50を表示
                posi_cav = (int)((((float)max_y-(float)dy)/(float)max_y)*(-50)+100);//同上
                // 画像の位置を設定する
                cImageView.layout(dx, dy, imgW, imgH);
                String str_apt = "apt="+posi_apt;
                textView_apt.setText(str_apt);
                String str_cav = "cav="+posi_cav;
                textView_cav.setText(str_cav);
                break;
            case MotionEvent.ACTION_DOWN:
                // nothing to do
                break;
            case MotionEvent.ACTION_UP:
                // nothing to do
                break;
            default:
                break;
        }
        // タッチした位置を古い位置とする
        preDx = newDx;
        preDy = newDy;

        //ここから、UDP送信スレッド
        new Thread(new Runnable() {
            public void run() {
                try {
                    // ソケットオープン
                    DatagramSocket sendUdpSocket = new DatagramSocket();
                    InetAddress IPAddress = InetAddress.getByName(“hogehoge");
                    int port = hogehoge;//ポート番号
                    String s_x = String.valueOf(posi_apt);
                    String s_y = String.valueOf(posi_cav);
                    String str = s_x + "," + s_y
                    byte[] sendData = str.getBytes("UTF-8");

                    // 送信先と送信データを設定 
                    DatagramPacket sendPacket = new DatagramPacket(
                            sendData, sendData.length, IPAddress, port);
                    // テキストデータを送信
                    sendUdpSocket.send(sendPacket);
                    // ソケットを閉じる　
                    sendUdpSocket.close();

                } catch (IOException e) {
                    System.out.println(e.getMessage());
                }
            }
        }).start();
        // 以上、UDP送信
        return true;
    }
}
```

■PC側のプログラムのソースコード
```c:udp_receive.cpp(winapi)
//参照サイト①：https://www.wabiapp.com/WabiSampleSource/windows/udp_server.html
//参照サイト②：https://www.kana-soft.com/tech/sample_0004.htm
//注意：▲部分はUDP処理と無関係なので、皆様の仕様に合わせて記述下さい。

//一般的なヘッダー
#include <windows.h>
#include <windowsX.h>
#include <stdio.h>
//▲その他、必要に応じて

//UDP関連
#pragma comment( lib, "ws2_32.lib" )//ライブラリ
#define WM_WINSOCKEVENT ( WM_USER + 100 )// WinSockからのイベント

//関数プロトタイプ、変数
static LRESULT CALLBACK	SoundProc( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam );
static void InitDialog( HWND hDlg );//▲
static HWND AppWnd;//▲アプリケーションのウインドウハンドル
static SOCKET oSocket;//UDP関連
static float udp_apt, udp_cav;//UDP受信するパラメータ
//▲その他、必要に応じて

int WINAPI	WinMain( HINSTANCE hInst, HINSTANCE hPreInst, LPSTR CmdLine, int show )
{
	//▲
	AppWnd   = NULL;
	DialogBox( hInst, MAKEINTRESOURCE(IDD_DIALOG_MAIN), NULL, (DLGPROC)SoundProc );
	return 0;
}

static LRESULT CALLBACK	SoundProc( HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam )
{
	switch( msg ){
	case WM_INITDIALOG://初期化全般
		InitDialog(hDlg);//▲ダイアログボックスの初期化

		//UDP初期化
		WSAData wsaData;
		WSAStartup(MAKEWORD(2, 0), &wsaData);   //(2, 0)はwinsockのバージョン
		//ソケットの生成
		oSocket = socket(AF_INET, SOCK_DGRAM, 0);  //AF_INETはIPv4、SOCK_DGRAMはUDP通信
		// ソケットを非同期にする
		WSAAsyncSelect(oSocket, hDlg, WM_WINSOCKEVENT, FD_READ | FD_CLOSE);
		// アドレス等格納
		struct sockaddr_in oSockAddr;  //IPv4
		oSockAddr.sin_family = AF_INET;
		oSockAddr.sin_port = htons(hogehoge);   //通信ポート番号設定
		oSockAddr.sin_addr.S_un.S_addr = INADDR_ANY; // INADDR_ANY：全アドレス受信
		// バインド
		::bind(oSocket, (struct sockaddr*)&oSockAddr, sizeof(oSockAddr));
		OutputDebugString("hogehoge");
		break;

	case WM_WINSOCKEVENT://UDPデータ受信
	{
		switch (WSAGETSELECTEVENT(lParam)) {
		case FD_READ:
		{
			struct sockaddr_in oFromAddr;
			int sockaddr_in_size = sizeof(struct sockaddr_in);
			CHAR szData[10];//Android側からの単位送信長は10キャラ未満
			//受信
			::recvfrom(
				oSocket
				, (char*)szData
				, sizeof(szData) - 1
				, 0
				, (struct sockaddr*)&oFromAddr
				, &sockaddr_in_size
				);
			//カンマ区切りの受信データを変数にストア
			//受信データは、[数値(apt),数値(cav)]の形式で受信
			sscanf(szData, "%f ,%f", &udp_apt, &udp_cav);
			//apertureによる華やかさの制御
			float apt, cav;//いずれも50～100の範囲
			apt = (-0.02*udp_apt + 2)*2;//0～2の範囲
			synth.midi_aperture_event(apt);//音色制御処理のコール
			//cavityによる華やかさの制御
			cav = -0.02*udp_cav + 4;//2～3の範囲
			synth.midi_cavity_event(cav);//音色制御処理のコール
//			OutputDebugString(szData);//★受信データの表示
		}
		break;
		}
	}
	break;
	//▲その他処理は必要に応じてcase文で追加（ダイアログボックスの制御など）
	default:
		break;
	}
	return FALSE;
}
```

■動作結果
【Android側】
・下記スクリーンショットのような画面が表示されます。
![Screenshot_20220513-161736.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2659855/04d994a3-8711-c9de-571b-396d98581304.png)
・下記リンク先のYouTube動画のように、トランペットアイコンを
　タップして動かした場合、画面のaptとcavの数値が変化すればOKです。
　　※YouTube動画リンク先：**https://youtu.be/9AdsV2z7p_E**

【PC側】　
・注意：Android（スマホ）とPCのWiFiをONにして下さい。
・送信確認ツール（Wireshark）を起動し、上記通りAndroidを
　タップするとUDP送信ログが表示されます。
・winapiコードの「★受信データの表示」の行のコメントアウトを
　外し、トランペットアイコンをタップして動かすと、
　VisualStudioの出力ウインドウに、下記のような表示が出ればOKです。
　表示例：90,95ﾌﾌﾌﾌﾌﾌﾌﾌﾌ88,90ﾌﾌﾌﾌﾌﾌﾌﾌﾌ
・あとは、取得した数値(90等)を使って、各自が作成された音処理系
　に渡せば（音色はともかく）同じような動作が実現できるはずです。
・通信不良の場合、IPaddrやポート番号（hogehogeと記載した部分）
　の設定ミス、WiFi未接続などが原因と思われます。


■今後の課題
winapi側（含む音処理）すべてをAndroid側にJNIベースで集約していく予定です。