0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-05-16

■プログラムの概要
 ・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で作成。

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>
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側のプログラムのソースコード

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
・下記リンク先の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ベースで集約していく予定です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?