树莓派UDP传输视频到安卓APP

服务端:
1.设备:树莓派、CSI摄像头、手机/平板。
2.功能:低延时实时图传。
3.实现:
用Python创建一个低延时的视频传输程序,读取CSI摄像头画面,实时捕获图像,设置分辨率大小为640*480,设置帧率为20;设置发送包压缩大小为30,将其发送到指定的UDP地址和端口号。
客户端:
1.Android Studio编程软件
2.创建
页面一:一个IP地址输入框、一个端口号输入框、一个确认按钮。
当点击按钮时,传递IP地址和端口号到第二页,并跳转到第二页
页面二: 获取第一页传递的IP地址和端口号,创建一个线程,在线程中接收UDP服务端发出的图像数据并播放出来,在活动销毁时关闭线程,释放资源。
问题:服务端Python运行正常,使用了PyCharm运行的客户端是可以接收到画面的,但如果是客户端Android Studio模拟器运行后输入IP和端口号,跳转第二页页面没出现视频画面。
帮忙看看,哪有问题,不怎么会Android_Studio编程。相关的代码是使用GPT构建实现,然后将相关报错一个一个的改的,但改完,程序是运行了,也没闪退啥的,就是无法实现功能,不知道什么原因。
有想过原因
1.树莓派UDP传过来的数据包,在app无法解析或者是解析的库是不对的。
2.UDP数据包的字节数,跟app设置的不一样啥的。
3.视频转成数据包后,在app解压时不完整。
4.app就没有接收到数据包。
有人解惑不

下面是相关的代码

服务端:

import cv2 
import numpy as np 
import socket 
# 定义UDP通信的IP地址和端口号 
UDP_IP_ADDRESS = "***.***.***.***." # 服务器端IP地址 
UDP_PORT_NO = **** # 端口号 
# 创建socket对象 
serverSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 
serverSock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serverSock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1024*1024*16)
# 设置视频捕获对象 
capture = cv2.VideoCapture(0) 
capture.set(3, 640) # 设置分辨率,宽度为640 
capture.set(4, 480) # 设置分辨率,高度为480 
capture.set(5, 15) # 设置帧率,每秒10帧
while True: 
    ret, frame = capture.read() 
    # 将捕获到的图像压缩成JPG格式 
    encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), 50] 
    result, encoded_img = cv2.imencode('.jpg', frame, encode_param) 
    # 将压缩后的图像数据转成byte类型 
    data = encoded_img.tobytes() 
    # 发送数据包 
    serverSock.sendto(data, (UDP_IP_ADDRESS, UDP_PORT_NO))

客户端
Android_Studio
app/manifests/AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <uses-permission android:name="android.permission.INTERNET"/>
    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyApplication"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".PlayerActivity"
            android:exported="true">
            <intent-filter android:scheme="http"
                tools:ignore="AppLinkUrlError">
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="video/*" />
            </intent-filter>
        </activity>
    </application>
</manifest>

app/java/com.example.myapplication/
MainActivity

package com.example.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
public class MainActivity extends AppCompatActivity {
    private EditText editTextIP;
    private EditText editTextPort;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        editTextIP = findViewById(R.id.editTextIP);
        editTextPort = findViewById(R.id.editTextPort);
        Button buttonConnect = findViewById(R.id.buttonConnect);
        buttonConnect.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String ip = editTextIP.getText().toString();
                String port = editTextPort.getText().toString();
                Intent intent = new Intent(MainActivity.this, PlayerActivity.class);
                intent.putExtra("ip", ip);
                intent.putExtra("port", port);
                startActivity(intent);
            }
        });
    }
}

app/java/com.example.myapplication/
PlayerActivity

package com.example.myapplication;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.widget.ImageView;
import androidx.appcompat.app.AppCompatActivity;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.Arrays;
import java.util.zip.InflaterInputStream;
public class PlayerActivity extends Activity {
    private ImageView imageView;
    private String address;
    private int port;
    private DatagramSocket socket;
    private Thread thread;
    private boolean running;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_player);
        // 获取IP地址和端口号
        Intent intent = getIntent();
        address = intent.getStringExtra("address");
        port = intent.getIntExtra("port", 0);
        imageView = (ImageView) findViewById(R.id.imageView);
        // 创建UDP套接字
        try {
            socket = new DatagramSocket();
            socket.setSoTimeout(2000);
        } catch (SocketException e) {
            e.printStackTrace();
        }
        // 创建接收图像数据的线程
        running = true;
        thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (running) {
                    try {
                        // 接收数据
                        byte[] buffer = new byte[65000];
                        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
                        socket.receive(packet);
                        // 解压缩图像数据
                        byte[] data = packet.getData();
                        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data);
                        InflaterInputStream inflaterInputStream = new InflaterInputStream(byteArrayInputStream);
                        byte[] buf = new byte[1024];
                        int len;
                        while ((len = inflaterInputStream.read(buf)) != -1) {
                            byteArrayOutputStream.write(buf, 0, len);
                        }
                        // 显示图像
                        Bitmap bitmap = BitmapFactory.decodeByteArray(byteArrayOutputStream.toByteArray(), 0, byteArrayOutputStream.size());
                        imageView.setImageBitmap(bitmap);
                    } catch (SocketTimeoutException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        thread.start();
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 关闭线程
        running = false;
        // 关闭UDP套接字
        socket.close();
    }
}

app/res/layout/
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <EditText
        android:id="@+id/editTextIP"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="IP Address"/>
    <EditText
        android:id="@+id/editTextPort"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Port"/>
    <Button
        android:id="@+id/buttonConnect"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Connect"/>
</LinearLayout>

app/res/layout/
activity_player.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView
        android:id="@+id/imageView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>

安卓的版本可能有要求的

  • 这篇博客: 计算机网络学习通习题中的 3 一个应用程序用UDP,到了IP层把数据报再划分为4个数据片并发送出去。结果前两个数据报片丢失,后两个到达目的站。过了一段时间应用程序重传UDP,而IP层仍然划分为4个数据报片来传送。结果这次前两个到达目的站而后两个丢失。试问:在目的站能否将这两次传输的4个数据报片组装成为完整的数据报?假定目的站第一次收到的后两个数据报片仍然保存在目的站的缓存中。 部分也许能够解决你的问题, 你可以仔细阅读以下内容或跳转源博客中阅读:
  • 正确答案:

    不行。因为:重传时IP数据报的标识字段有另外一个标识符。标识符相同的IP数据报片才能组装成一个IP数据报。前两个IP数据报片的标识符与后两个IP数据报片的标识符不同,所以不能组装成一个IP数据报。

TechWhizKid参考GPT回答:

  • Android 客户端代码,几个可能引起问题的地方:
  1. IP地址和端口传递问题:在 MainActivity 中,你获取了 EditText 的文本并尝试把它转换成字符串。对于 IP 地址,这是没有问题的。但是对于端口,你也尝试把它转换为字符串,然后在 PlayerActivity 中尝试将这个字符串转换为整型,这可能会引起问题。当你从 MainActivity 向 PlayerActivity 传递端口号时,你应该首先将其转化为整型:

    String ip = editTextIP.getText().toString();
    int port = Integer.parseInt(editTextPort.getText().toString());
    Intent intent = new Intent(MainActivity.this, PlayerActivity.class);
    intent.putExtra("ip", ip);
    intent.putExtra("port", port);
    startActivity(intent);
    
  2. UDP接收数据解压问题:在服务端,你使用了JPEG压缩来压缩图像,但在 Android 客户端的 PlayerActivity 中,你尝试使用InflaterInputStream来解压接收到的数据。这可能是因为数据不能正确解压,所以没有图像显示。你应该使用与服务端相同的解压方式,也就是直接解码JPEG图像:

    Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, packet.getLength());
    imageView.setImageBitmap(bitmap);
    
  3. 接收数据更新UI问题:你尝试在接收线程中直接更新 UI,但这在 Android 中是禁止的。你需要使用 runOnUiThread 方法来更新 UI:

    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            imageView.setImageBitmap(bitmap);
        }
    });
    

给一个看上去不太专业,但是实际情况下常出现的可能:Android Studio毕竟不是真机,可能真机就能跑了,之前做过一个app,Android Studio中连服务主机都连不上(确认Android studio虚拟机联网了,并且浏览器打开网站正常),结果,打包apk后,真机一试,没问题了😂🦊

建议你采用服务器进行类似的直播推流,将树莓派的视频推到服务器,安卓再拉取直播流

用wireshark抓包软件抓下包,看是不是传输过程丢包了?还是程序中有问题,数据包没发送出来~

可以在Android Studio中使用Debug工具来单步调试客户端代码,以查看它是否正确地接收了数据包。
使用第三方的视频播放库,例如Vitamio,来播放来自服务端的视频数据。

我先初步分析下,再有问题发我或者私信

服务端发送数据时设置了发送包压缩大小为30,而在客户端收到数据时没有对数据进行解压缩,因此无法正常解码。建议在客户端的代码中对数据进行解压缩,使用与服务端相同的压缩格式对数据进行解码。
其次,您没有提供客户端的相关代码,因此无法确定客户端代码的问题。您可以确认是否已正确获取IP地址和端口号,并且您的代码是否正确将接收到的数据解码为图像并进行显示。以下是一个简单的客户端代码示例,供您参考:

public class PlayerActivity extends AppCompatActivity {
    private SurfaceView surfaceView;
    private SurfaceHolder surfaceHolder;
    private ImageView imageView;
    private DatagramSocket clientSocket;
    private String udpIp;
    private int udpPort;
    private boolean isRunning = true;
    private ExecutorService executorService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_player);
        Intent intent = getIntent();
        udpIp = intent.getStringExtra("udp_ip");
        udpPort = intent.getIntExtra("udp_port", 0);
        surfaceView = findViewById(R.id.surface_view);
        imageView = findViewById(R.id.imageView);
        surfaceHolder = surfaceView.getHolder();
        executorService = Executors.newSingleThreadExecutor();
        executorService.execute(new DataReceiver());
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        isRunning = false;
        executorService.shutdown();
        try {
            clientSocket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private class DataReceiver implements Runnable {
        private Paint paint = new Paint();
        private Bitmap bitmap;

        public DataReceiver() {
            paint.setStyle(Paint.Style.FILL);
            paint.setColor(Color.WHITE);
            paint.setTextSize(20);
        }

        @Override
        public void run() {
            int width = 640;
            int height = 480;
            int bufferSize = width * height * 3;
            byte[] buffer = new byte[bufferSize];
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inPreferredConfig = Bitmap.Config.RGB_565;
            try {
                clientSocket = new DatagramSocket(udpPort);
                while (isRunning) {
                    DatagramPacket packet = new DatagramPacket(buffer, bufferSize);
                    clientSocket.receive(packet);
                    byte[] data = packet.getData();
                    ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(data);
                    try {
                        bitmap = BitmapFactory.decodeStream(byteArrayInputStream, null, options);
                        if (bitmap != null) {
                            Canvas canvas = surfaceHolder.lockCanvas();
                            if (canvas != null) {
                                canvas.drawBitmap(bitmap, 0, 0, null);
                                surfaceHolder.unlockCanvasAndPost(canvas);
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

在以上代码中,DataReceiver 类是接收 UDP 数据的线程。由于视频数据是压缩的,需要使用 BitmapFactory 对象创建 Bitmap 对象,并将其绘制到画布上以显示图像。需要注意的是,这段代码仅供参考,您需要根据您的具体需求进行修改。

树莓派UDP传输视频到安卓APP

https://blog.csdn.net/Dontla/article/details/109844290

  1. 在树莓派上安装OpenCV,使用以下命令:
sudo apt-get install python-opencv

2. 在树莓派上编写Python代码,以打开摄像头并将视频流传输到UDP套接字。以下是一个示例代码:

import cv2
   import socket
   import pickle
   import struct
    # Set up socket
   client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
   host_ip = '192.168.0.100' # Replace with your Android device's IP address
   port = 9999
   client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # Open camera
   cap = cv2.VideoCapture(0)
    while True:
       # Read frame from camera
       ret, frame = cap.read()
        # Serialize frame
       data = pickle.dumps(frame)
        # Pack message and send
       message = struct.pack("Q", len(data)) + data
       client_socket.sendto(message, (host_ip, port))
    # Release resources
   cap.release()
   client_socket.close()


这个代码将打开摄像头并使用OpenCV从中读取帧。然后,它将使用pickle模块将帧序列化为字节字符串,并使用struct模块将其打包为UDP数据包。最后,它将数据包发送到指定的IP地址和端口。 
 3. 在安卓应用程序中编写代码,以接收UDP数据包,并使用OpenCV显示视频流。以下是一个示例代码:

import android.os.Bundle;
   import android.support.v7.app.AppCompatActivity;
   import android.view.WindowManager;
   import org.opencv.android.BaseLoaderCallback;
   import org.opencv.android.LoaderCallbackInterface;
   import org.opencv.android.OpenCVLoader;
   import org.opencv.core.CvType;
   import org.opencv.core.Mat;
   import org.opencv.imgproc.Imgproc;
   import org.opencv.videoio.VideoCapture;
   import java.net.DatagramPacket;
   import java.net.DatagramSocket;
   import java.net.InetAddress;
   import java.nio.ByteBuffer;
    public class MainActivity extends AppCompatActivity {
       private VideoCapture videoCapture;
       private Mat frame;
       private boolean isPlaying;
       private BaseLoaderCallback loaderCallback = new BaseLoaderCallback(this) {
           @Override
           public void onManagerConnected(int status) {
               switch (status) {
                   case LoaderCallbackInterface.SUCCESS:
                       videoCapture = new VideoCapture();
                       isPlaying = true;
                       new Thread(new Runnable() {
                           @Override
                           public void run() {
                               try {
                                   DatagramSocket socket = new DatagramSocket(9999);
                                   byte[] buffer = new byte[65507];
                                   DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
                                   while (isPlaying) {
                                       socket.receive(packet);
                                       byte[] data = packet.getData();
                                       ByteBuffer bb = ByteBuffer.wrap(data);
                                       long length = bb.getLong();
                                       byte[] imageData = new byte[(int) length];
                                       bb.get(imageData, 0, imageData.length);
                                       frame = new Mat(480, 640, CvType.CV_8UC3);
                                       frame.put(0, 0, imageData);
                                       Imgproc.cvtColor(frame, frame, Imgproc.COLOR_RGB2BGR);
                                       runOnUiThread(new Runnable() {
                                           @Override
                                           public void run() {
                                               showFrame();
                                           }
                                       });
                                   }
                                   socket.close();
                               } catch (Exception e) {
                                   e.printStackTrace();
                               }
                           }
                       }).start();
                       break;
                   default:
                       super.onManagerConnected(status);
                       break;
               }
           }
       };
        @Override
       protected void onCreate(Bundle savedInstanceState) {
           super.onCreate(savedInstanceState);
           getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
           setContentView(R.layout.activity_main);
       }
        @Override
       protected void onResume() {
           super.onResume();
           OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_4_0, this, loaderCallback);
       }
        @Override
       protected void onPause() {
           super.onPause();
           isPlaying = false;
           if (videoCapture != null) {
               videoCapture.release();
               videoCapture = null;
           }
       }
        private void showFrame() {
           if (frame != null) {
               if (videoCapture.isOpened()) {
                   videoCapture.release();
               }
               videoCapture.open(0);
               videoCapture.set(Highgui.CV_CAP_PROP_FRAME_WIDTH, 640);
               videoCapture.set(Highgui.CV_CAP_PROP_FRAME_HEIGHT, 480);
               videoCapture.grab();
               videoCapture.retrieve(frame);
               Imgproc.cvtColor(frame, frame, Imgproc.COLOR_BGR2RGB);

pycharm中可以接收到视频画面,而安卓模app中不行,首先要确保树莓派和安卓APP连接到同一个网络,并且网络连接正常。确保视频编码在树莓派和安卓APP上都支持,并且视频数据可以正确解码。或者更换tcp看看

可以采用直接推流的方式试试

搞个服务器,先上传到服务器,然后app再从服务器中请求视频