You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将Android(Kotlin)相机预览转为视频流并传输至C#程序?

安卓相机预览流转C#程序解决方案

一、相机预览帧捕获与编码

你当前基于CameraX实现了预览和拍照功能,要获取可传输的流,需要新增ImageAnalysis用例来捕获实时预览帧,再通过硬件编码将原始帧转为适合网络传输的格式(推荐H.264,压缩率高、兼容性好)。以下是修改后的CameraManager核心代码:

修改CameraManager添加帧捕获逻辑

// 新增ImageAnalysis成员变量
private lateinit var imageAnalysis: ImageAnalysis
// 用于网络传输的输出流(后续初始化)
private var outputStream: OutputStream? = null

private fun bindCameraUseCases() {
    cameraProvider?.let { cameraProvider ->
        val cameraSelector = getCameraSelector()
        val previewView = getPreviewUseCase()
        imageCapture = getImageCapture()
        
        // 初始化ImageAnalysis,设置只保留最新帧避免阻塞
        imageAnalysis = ImageAnalysis.Builder()
            .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
            .build()
        
        // 设置帧分析器,在后台线程处理帧编码与传输
        imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(context)) { imageProxy ->
            processAndSendFrame(imageProxy)
            imageProxy.close() // 必须关闭,否则相机资源泄漏
        }

        cameraProvider.unbindAll()
        try {
            camera = cameraProvider.bindToLifecycle(
                owner,
                cameraSelector,
                previewView,
                imageCapture,
                imageAnalysis // 绑定新增的帧捕获用例
            )
            camera?.cameraControl?.setLinearZoom(0.0f)
            previewView.setSurfaceProvider(viewPreview.surfaceProvider)

        } catch (exc: Exception) {
            Log.e(TAG, "Use case binding failed $exc")
        }
    } ?: throw IllegalStateException("Camera initialization failed.")
}

// 处理帧并编码为H.264后发送
private fun processAndSendFrame(imageProxy: ImageProxy) {
    val image = imageProxy.image ?: return
    // 提取YUV格式帧数据(CameraX默认输出YUV_420_888)
    val yPlane = image.planes[0]
    val uPlane = image.planes[1]
    val vPlane = image.planes[2]
    
    // 将YUV数据转为字节数组(简化示例,实际可直接传入MediaCodec编码)
    val yBuffer = yPlane.buffer
    val uBuffer = uPlane.buffer
    val vBuffer = vPlane.buffer
    val yData = ByteArray(yBuffer.remaining())
    val uData = ByteArray(uBuffer.remaining())
    val vData = ByteArray(vBuffer.remaining())
    yBuffer.get(yData)
    uBuffer.get(uData)
    vBuffer.get(vData)
    
    // 这里省略MediaCodec硬件编码H.264的逻辑(需提前初始化MediaCodec)
    // 编码后得到h264Bytes,调用sendFrame发送
    // sendFrame(h264Bytes)
}

// 发送帧数据到C#端
private fun sendFrame(data: ByteArray) {
    outputStream?.run {
        try {
            // 先发送帧长度,方便C#端解析
            val lengthBytes = ByteBuffer.allocate(4).putInt(data.size).array()
            write(lengthBytes)
            write(data)
            flush()
        } catch (e: IOException) {
            Log.e(TAG, "发送失败: ${e.message}")
        }
    }
}

二、网络协议选择

推荐两种适配场景的方案:

  • TCP协议:传输可靠、延迟适中,适合需要稳定画面的场景(如远程协助)。安卓端用Socket,C#端用TcpListener/TcpClient。
  • UDP协议:延迟极低但可能丢包,适合实时性要求极高的场景(如实时监控)。安卓端用DatagramSocket,C#端用UdpClient。

安卓端TCP连接初始化示例

// 在Activity或Manager中初始化连接(建议用协程避免阻塞UI)
fun connectToWindowsServer(ip: String, port: Int) {
    CoroutineScope(Dispatchers.IO).launch {
        try {
            val socket = Socket(ip, port)
            outputStream = socket.getOutputStream()
            Log.d(TAG, "已连接到Windows服务器")
        } catch (e: IOException) {
            Log.e(TAG, "连接失败: ${e.message}")
        }
    }
}

三、C#端接收与解码

以TCP接收为例,C#端需要接收字节流,解码H.264并显示画面(可借助OpenCVSharp或FFmpeg实现解码):

C# TCP接收与显示示例

using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using OpenCvSharp;

class AndroidCameraStreamReceiver
{
    private TcpListener _listener;
    private TcpClient _client;
    private NetworkStream _stream;

    public void StartListening(int port)
    {
        _listener = new TcpListener(IPAddress.Any, port);
        _listener.Start();
        Console.WriteLine("等待安卓设备连接...");
        _client = _listener.AcceptTcpClient();
        _stream = _client.GetStream();
        var receiveThread = new Thread(ReceiveAndDisplayFrames);
        receiveThread.Start();
    }

    private void ReceiveAndDisplayFrames()
    {
        var lengthBuffer = new byte[4];
        while (true)
        {
            // 读取帧长度
            var bytesRead = _stream.Read(lengthBuffer, 0, 4);
            if (bytesRead != 4) break;
            var frameLength = BitConverter.ToInt32(lengthBuffer, 0);
            
            // 读取帧数据
            var frameData = new byte[frameLength];
            bytesRead = _stream.Read(frameData, 0, frameLength);
            if (bytesRead != frameLength) break;
            
            // 解码H.264帧并显示(简化示例,实际需用解码器处理)
            using var mat = new Mat();
            // 此处省略H.264解码逻辑,可借助OpenCV的VideoCapture或FFmpeg库
            Cv2.ImShow("安卓相机预览", mat);
            Cv2.WaitKey(1);
        }
    }
}

关键注意事项

  • 线程隔离:编码、网络传输必须放在后台线程,安卓用协程,C#用线程池,避免阻塞UI或主线程。
  • 权限申请:安卓端需动态申请CAMERA和INTERNET权限,否则无法正常捕获帧和传输数据。
  • 资源释放:安卓退出时要关闭Socket、MediaCodec和CameraX绑定的用例;C#端关闭TcpClient和Listener,避免资源泄漏。
  • 编码优化:优先使用MediaCodec硬件编码,比软件编码性能提升数倍,适配中高端安卓设备。

内容的提问来源于stack exchange,提问作者Pantastix

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 04:07:17