QT和Android之间的UDP图传

前言

第一次发博客,多多少少存在不妥的地方,哈哈哈,不多废话了,开摆。
做图传的目的是大学期间想自己做一套智能家居系统,其中当然少不了移动端的App啦,家用监控摄像头是其中必不可少的环节,毕竟现在家里有几个摄像头想随时看看家里正在发生什么也是很正常的事。

实现功能

实现QT到Android的图传,QT到QT的图传,Android到QT的图传,Android到Android的图传。

1.QT到QT的图像传输

发送端
首先,要在QT使用UDP得先在 .pro中添加netword模块

QT       += core gui network

之后进行UI设计,这是我的UI,有点丑哈哈哈,别介意

在这里插入图片描述
就只有一个label用于展示即将要发送的图片,和一个按钮,作用是点击发送图片

之后自己寻找一个图片素材,添加到QT的resource资源文件中,我找了一张小猫咪的图片,为什么要找小猫咪呢?因为小猫咪太可爱了!

之后开始初始化UDP,绑定发送串口号

QUdpSocket *mudpSocket;
mudpSocket = new QUdpSocket(this);
mudpSocket->bind(8081);
connect(mudpSocket, &QUdpSocket::readyRead,
             this, &MainWindow::readPendingDatagrams);

因为这是发送端,我在这个工程中并没有完善readPendingDatagrams()函数

把之前找到的素材图片显示在label中

QImage *img=new QImage; //新建一个image对象
img->load(":/cat/cat.jpeg"); //将图像资源载入对象img,注意路径,可点进图片右键复制路径
ui->label->setPixmap(QPixmap::fromImage(*img)); //将图片放入label,使用setPixmap,注意指针*img

运行后如下图
在这里插入图片描述
小猫咪是不是特别可爱啊,哈哈哈哈

那么接下来就到了最为重要的发送环节了,我们在按钮点击事件函数中进行图片发送

void MainWindow::on_pushButton_clicked()
{
    quint16 port = 8080;
    QHostAddress addr;
    addr.setAddress("192.168.1.7");
    QImage *image=new QImage; //新建一个image对象
    image->load(":/cat/cat.jpeg"); //将图像资源载入对象img,注意路径,可点进图片右键复制路径

    QByteArray byte;
    QBuffer buff(&byte);
    buff.open(QIODevice::ReadWrite);
    image->save(&buff,"JPEG",50);
    //QByteArray zipimage = qCompress(byte,5);
    cout << byte.size() << endl;
    //cout << zipimage.size() << endl;
    mudpSocket->writeDatagram(byte,addr,port);
    mudpSocket->flush();
}

就不多解释了,主要就是把图像QImage转化为Byte格式,在发送出去

接收端

接收端的UDP的初始化和发送端一样( 注意接收端的串口得绑定发送端发送图片的串口号 ),UI设计就是发送端少了一个按钮,也就是说只有一个label控件显示图片

那么直接进入正题,接收端的槽函数MainWindow::readPendingDatagrams()

void MainWindow::readPendingDatagrams()
 {
    quint64 size = mudpSocket->pendingDatagramSize();
    QByteArray buff;
    buff.resize(size);
    mudpSocket->readDatagram(buff.data(),buff.size());
    //buff = qUncompress(buff);
    QBuffer buffer(&buff);
    QImageReader reader(&buffer,"JPEG");//可读入磁盘文件、设备文件中的图像、以及其他图像数据如pixmap和image,相比较更加专业。
    //buffer属于设备文件一类,
    QImage image = reader.read();//read()方法用来读取设备图像,也可读取视频,读取成功返回QImage*,否则返回NULL
    ui->label->setPixmap(QPixmap::fromImage(image));
    ui->label->resize(image.width(),image.height());
 }

然后运行发送端和接收端,点击按钮发送
在这里插入图片描述
滴~~,发送
在这里插入图片描述
成功啦!!!!!

2.Android到Android的图象传输

使用手机摄像头获得视频流,之后实时传输给另一个手机并显示,那么有人问,为什么你QT就只发送一张图片呢? 哎呀,我不会告诉你我笔记本没有内置摄像头啦,呜呜呜~~

发送端
我这里的Android的UI设计的比较简单,就一个SurfaceView用于实时显示摄像头捕捉到的图像信息

<SurfaceView
        android:id="@+id/vedio"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

好的,接下来就是MainActivity代码的编写了,不过在这之前,得先申请可能用到的权限

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA"/>

在Android 6.0之后,需要动态申请权限

/**
     * android 6.0 以上需要动态申请权限
     */
    private void initPermission() {
        String permissions[] = {Manifest.permission.ACCESS_WIFI_STATE,
                Manifest.permission.CHANGE_WIFI_STATE,
                Manifest.permission.INTERNET,
                Manifest.permission.WAKE_LOCK,
                Manifest.permission.CAMERA
        };

        ArrayList<String> toApplyList = new ArrayList<String>();

        for (String perm : permissions) {
            if (PackageManager.PERMISSION_GRANTED != ContextCompat.checkSelfPermission(this, perm)) {
                toApplyList.add(perm);
                // 进入到这里代表没有权限.
                Toast.makeText(this,"没有权限",Toast.LENGTH_SHORT).show();
            }
        }
        String tmpList[] = new String[toApplyList.size()];
        if (!toApplyList.isEmpty()) {
            ActivityCompat.requestPermissions(this, toApplyList.toArray(tmpList), 123);
        }

    }

    /**
     * 权限申请回调,可以作进一步处理
     * @param requestCode
     * @param permissions
     * @param grantResults
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        // 此处为android 6.0以上动态授权的回调,用户自行实现。
    }

好的,一切都准备就绪了,接下来可以编写MainActivity了

我们更希望图像能够全屏展示,所以得隐藏标题栏

ActionBar actionBar=getSupportActionBar();
if(actionBar!=null){
     actionBar.hide();
}

另外,把MainActivity设置为横屏显示

android:screenOrientation="landscape"

相机参数在surfaceCreated中进行初始化,并且在onPreviewFrame回调中对相机返回的图像数据进行处理,唉,算了,不讲了,直接上代码吧,害怕我给把自己讲晕了

MainActivity完整代码如下:

package com.example.myapplication;

import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.hardware.Camera;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.LinkedList;

public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback, Camera.PreviewCallback{

    public SendHandler sendHandler = new SendHandler();
    private ReceiveHandler receiveHandler = new ReceiveHandler();
    public DatagramSocket sendSocket;
    private DatagramSocket receiveSocket;
    public InetAddress serverAddr;

    private final static String SEND_IP = "192.168.1.5";
    private final static int SEND_PORT = 8080;
    private final static int RECEIVE_PORT = 8089;            //接收端口号

    private boolean listenStatus_recieve = true;  //接收线程的循环标识
    private boolean listenStatus_send = true;  //发送线程的循环标识
    private byte[] receiveInfo;     //接收报文信息
    private byte[] buf;             //发送报文信息

    private String receiveContent;

    private Camera mCamera;
    private Camera.Size previewSize;            //预览图像的宽高

    private long lastSendTime = 0;                  //上一次发送图像帧的时间
    private final LinkedList<DatagramPacket> packetList = new LinkedList<>();   //图像数据包队列

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ActionBar actionBar=getSupportActionBar();
        if(actionBar!=null){
            actionBar.hide();
        }
        requestPower();
        SurfaceView surfaceView = (SurfaceView) findViewById(R.id.vedio);
        SurfaceHolder holder = surfaceView.getHolder();
        holder.setKeepScreenOn(true);  //保持屏幕常亮
        holder.addCallback(this);

        new UDPsend().start();
    }

    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
        //获取相机
        if (mCamera == null) {
            mCamera = Camera.open();    //打开后摄像头
            Camera.Parameters parameters = mCamera.getParameters();

            //设置预览图大小
            //注意必须为parameters.getSupportedPreviewSizes()中的长宽,否则会报异常
            //只能设置系统能支持的宽高参数。  参数不对摄像头无法调用
            int s_width=1280;
            int s_height=720;
            parameters.setPictureSize(s_width, s_height);//如果不设置会按照系统默认配置最低160x120分辨率
            parameters.setPreviewSize(s_width, s_height);

            previewSize = parameters.getPreviewSize();
            //设置自动对焦
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
            mCamera.setParameters(parameters);
            mCamera.cancelAutoFocus();
            //设置回调
            try {
                mCamera.setPreviewDisplay(surfaceHolder);
                mCamera.setPreviewCallback(this);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //开始预览
        mCamera.startPreview();
    }

    @Override
    public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int i, int i1, int i2) {

    }

    @Override
    public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
        //释放相机
        if (mCamera != null) {
            mCamera.setPreviewCallback(null);
            mCamera.stopPreview();
            mCamera.release();
            mCamera = null;
        }
    }

    @Override
    public void onPreviewFrame(byte[] bytes, Camera camera) {
        long curTime = System.currentTimeMillis();
        //每20毫秒发送一帧
        if (curTime - lastSendTime >= 50) {
            lastSendTime = curTime;
            //NV21格式转JPEG格式
            YuvImage image = new YuvImage(bytes, ImageFormat.NV21, previewSize.width ,previewSize.height, null);
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            image.compressToJpeg(new Rect(0, 0, previewSize.width, previewSize.height), 20, bos);
            Log.d("大小:",""+bos.size());
            int packMaxSize = 65500;    //防止超过UDP包的最大大小
            byte[] imgBytes = bos.toByteArray();
            //打包
            DatagramPacket packet = new DatagramPacket(imgBytes, imgBytes.length > packMaxSize ? packMaxSize : imgBytes.length,
                    serverAddr, SEND_PORT);
            //添加到队尾
            synchronized (packetList) {
                packetList.addLast(packet);
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //停止接收线程,关闭套接字连接
        listenStatus_recieve = false;
        listenStatus_send = false;
        receiveSocket.close();
        sendSocket.close();
    }

    class SendHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 1:
                    Toast.makeText(MainActivity.this, "成功发送", Toast.LENGTH_SHORT).show();
                    break;
                case 2:
                    Toast.makeText(MainActivity.this, "发送失败", Toast.LENGTH_SHORT).show();
                    break;
                default:
                    break;
            }

        }
    }

    class ReceiveHandler extends Handler{
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            receiveContent = new String(receiveInfo, 0, receiveInfo.length);
            Toast.makeText(MainActivity.this, ""+receiveContent, Toast.LENGTH_SHORT).show();
        }
    }

    public class UDPsend extends Thread{

        @Override
        public void run() {
            buf="i am an android developer, hello android! ".getBytes();
            while (listenStatus_send){
                try {
                    // 创建DatagramSocket对象,使用随机端口
                    sendSocket = new DatagramSocket();
                    serverAddr = InetAddress.getByName(SEND_IP);
                    DatagramPacket outPacket;

                    synchronized (packetList) {
                        //没有待发送的包
                        if (packetList.isEmpty()) {
                            try {
                                Thread.sleep(10);
                                continue;
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        outPacket = packetList.getFirst();
                        packetList.removeFirst();
                    }
                    try {
                        sendSocket.send(outPacket);
                        //sendHandler.sendEmptyMessage(1);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                } catch (Exception e) {
                    sendHandler.sendEmptyMessage(2);
                    e.printStackTrace();
                }
            }
        }
    }

    /*
     *   UDP数据接收线程
     * */
    public class UdpReceiveThread extends Thread
    {
        @Override
        public void run()
        {
            try
            {
                receiveSocket = new DatagramSocket(RECEIVE_PORT);

                while(listenStatus_recieve)
                {
                    byte[] inBuf= new byte[1024];
                    DatagramPacket inPacket=new DatagramPacket(inBuf,inBuf.length);
                    receiveSocket.receive(inPacket);
                    receiveInfo = inPacket.getData();
                    receiveHandler.sendEmptyMessage(1);
                }
            } catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    }

    /*
    * 动态权限申请
     */
    public void requestPower() {
        //判断是否已经赋予权限
        if (ContextCompat.checkSelfPermission(this,
                Manifest.permission.CAMERA)
                != PackageManager.PERMISSION_GRANTED) {
            //如果应用之前请求过此权限但用户拒绝了请求,此方法将返回 true。
            if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                    Manifest.permission.CAMERA)) {//这里可以写个对话框之类的项向用户解释为什么要申请权限,并在对话框的确认键后续再次申请权限.它在用户选择"不再询问"的情况下返回false
            } else {
                //申请权限,字符串数组内是一个或多个要申请的权限,1是申请权限结果的返回参数,在onRequestPermissionsResult可以得知申请结果
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.CAMERA,}, 1);
            }
        }
    }

}

细心的朋友可能发现了,最后的动态申请权限有点不一样,哎呀,都差不多啦,能跑就行,不过还是建议大家用上面的那个,确实是别人家的孩子写的代码,另外,大家可以忽视UDP的接收线程,我只是用来测试的,哈哈哈!

发送端就到这里了。

接收端

UI只有一个ImageView

<ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/myvediorecive"/>

也像发送端那样设置成横屏显示,并且添加权限

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET"/>

MainActivity 代码如下

package com.baidu.myapplication;

import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.graphics.YuvImage;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.util.Size;
import android.widget.ImageView;
import android.widget.Toast;

import java.io.ByteArrayOutputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class MainActivity extends AppCompatActivity {
    private DatagramSocket receiveSocket;
    private ImageView imageView;
    private final static int RECEIVE_PORT = 8080;            //接收端口号
    private boolean listenStatus_recieve = true;  //接收线程的循环标识
    private byte[] receiveInfo;     //接收报文信息
    private ReceiveHandler receiveHandler = new ReceiveHandler();
    private String receiveContent;
    private Bitmap bitmap;
    private int imagewidth;
    private int imageheigth;

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

        ActionBar actionBar=getSupportActionBar();
        if(actionBar!=null){
            actionBar.hide();
        }

        imageView = findViewById(R.id.myvediorecive);
        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.cat);
        imageView.setImageBitmap(bitmap);
        imagewidth = bitmap.getWidth();
        imageheigth = bitmap.getHeight();
        new UdpReceiveThread().start();
    }

    class ReceiveHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //receiveContent = new String(receiveInfo, 0, receiveInfo.length);
            imageView.setImageBitmap(bitmap);
            //Toast.makeText(MainActivity.this, "yes", Toast.LENGTH_SHORT).show();
        }
    }

    /*
     *   UDP数据接收线程
     * */
    public class UdpReceiveThread extends Thread
    {
        @Override
        public void run()
        {
            try
            {
                receiveSocket = new DatagramSocket(RECEIVE_PORT);

                while(listenStatus_recieve)
                {
                    byte[] inBuf= new byte[65500];
                    DatagramPacket inPacket=new DatagramPacket(inBuf,inBuf.length);
                    receiveSocket.receive(inPacket);
                    receiveInfo = inPacket.getData();

                    ByteArrayOutputStream stream = new ByteArrayOutputStream();
                    stream.write(inBuf);
                    Log.d("大小",""+stream.toByteArray().length);
                    bitmap = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size());
                    receiveHandler.sendEmptyMessage(1);
                    stream.close();
                }
            } catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        listenStatus_recieve = false;
        receiveSocket.close();
    }
}

一切都完成了,试着运行一下
发送端:
在这里插入图片描述
接收端:
在这里插入图片描述
没问题!!!!!

Android到QT的图传

这个时候我们已经完成了QT的发送和接收以及Android端的发送和接收,那么,可不可以用以上代码进行Android到QT的图传呢?我的回答是可以的,只需要改一下串口号和IP地址就可以直接发送和接收了

QT到Android的图传

这个时候我们已经完成了QT的发送和接收以及Android端的发送和接收,那么,可不可以用以上代码进行QT到Android的图传呢?我的回答是可以的,只需要改一下串口号和IP地址就可以直接发送和接收了

复读机,哈哈哈

温馨提示

UDP一次性发送的数据比较少,如果你的相机或者图片质量很高,那么接收端收到的图片可能就不是完整的,这时你可以降低质量参数,或者分包发送。

项目链接

项目链接

  • 8
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值