Android实时获取摄像头画面传输至PC端

这篇具有很好参考价值的文章主要介绍了Android实时获取摄像头画面传输至PC端。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

最近在做一个PC端小应用,需要获取摄像头画面,但是电脑摄像头像素太低,而且位置调整不方便,又不想为此单独买个摄像头。于是想起了之前淘汰掉的手机,成像质量还是杠杠的,能不能把手机摄像头连接到电脑上使用呢?经过搜索,在网上找到了几款这类应用,但是都是闭源的。我一向偏好使用开源软件,但是找了挺久也没有找到一个比较合适的。想着算了,自己开发一个吧,反正这么个简单的需求,应该大概也许不难吧(🐶

思路

通过Android的Camera API是可以拿到摄像头每一帧的原始图像数据的,一般都是YUV格式的数据,一帧2400x1080的图片大小为2400x1080x3/2字节,约等于3.7M。25fps的话,带宽要达到741mbps,太费带宽了,所以只能压缩一下再传输了。最简单的方法,把每一帧压缩成jpeg再传输,就是效率有点低,而更好的方法是压缩成视频流后再传输,PC端接收到视频流后再实时解压缩还原回图片。

实现

思路有了,那就开搞吧。

获取摄像头数据

新建一个Android项目,然后在AndroidManifest.xml中声明摄像头和网络权限:

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

界面上搞一个SurfaceView用于预览

<SurfaceView
            android:id="@+id/surfaceview"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent" />

进入主Activity时,打开摄像头:

private void openCamera(int cameraId) {
    class CameraHandlerThread extends HandlerThread {

        private Handler mHandler;

        public CameraHandlerThread(String name) {
            super(name);
            start();
            mHandler = new Handler(getLooper());
        }

        synchronized void notifyCameraOpened() {
            notify();
        }

        void openCamera() {
            mHandler.post(() -> {
                camera = Camera.open(cameraId);
                notifyCameraOpened();
            });
            try {
                wait();
            } catch (InterruptedException e) {
                Log.w(TAG, "wait was interrupted");
            }
        }
    }
    if (camera == null) {
        CameraHandlerThread mThread = new CameraHandlerThread("camera thread");
        synchronized (mThread) {
            mThread.openCamera();
        }
    }
}

然后绑定预览surface并调用摄像头预览接口开始获取摄像头数据:

camera.setPreviewDisplay(surfaceHolder);
buffer.data = new byte[bufferSize];
camera.setPreviewCallbackWithBuffer(this);
camera.addCallbackBuffer(buffer.data);
camera.startPreview();

每一帧图像的数据准备好后,会通过onPreviewFrame回调把YUV数据传送过来,处理完后,一定要再调一次addCallbackBuffer以获取下一帧的数据。

@Override
public void onPreviewFrame(byte[] data, Camera c) {
    // data就是原始YUV数据
    // 这里处理YUV数据
    camera.addCallbackBuffer(buffer.data);
}

监听PC端连接

直接用ServerSocket就行了,反正也不需要考虑高并发场景。

try (ServerSocket srvSocket = new ServerSocket(6666)) {
    this.socketServer = srvSocket;
    for (; ; ) {
        Socket socket = srvSocket.accept();
        this.outputStream = new DataOutputStream(socket.getOutputStream());
        // 初始化视频编码器
    }
} catch (IOException ex) {
    Log.e(TAG, ex.getMessage(), ex);
}

视频编码

Android上可以使用系统自带的MediaCodec实现视频编解码,但是这里我并不打算使用它,而是使用灵活度更高的ffmpeg(谁知道后面有没有一些奇奇怪怪的需求🐶🐶🐶)。 网上已经有大神封装好适用于Android的ffmpeg了,直接在Gradle上引用javacv库就行。

configurations {
    javacpp
}

task javacppExtract(type: Copy) {
    dependsOn configurations.javacpp

    from { configurations.javacpp.collect { zipTree(it) } }
    include "lib/**"
    into "$buildDir/javacpp/"
    android.sourceSets.main.jniLibs.srcDirs += ["$buildDir/javacpp/lib/"]

    tasks.getByName('preBuild').dependsOn javacppExtract
}
dependencies {
    implementation group: 'org.bytedeco', name: 'javacv', version: '1.5.9'
    javacpp group: 'org.bytedeco', name: 'openblas-platform', version: '0.3.23-1.5.9'
    javacpp group: 'org.bytedeco', name: 'opencv-platform', version: '4.7.0-1.5.9'
    javacpp group: 'org.bytedeco', name: 'ffmpeg-platform', version: '6.0-1.5.9'
}

javacv库自带了一个FFmpegFrameRecorder类可以实现视频录制功能,但是灵活度太低,还是直接调原生ffmpeg接口吧。

初始化H264编码器:

public void init(int width, int height, int[] preferredPixFmt) throws IOException {
    int bitRate = width * height * 3 / 2 * 16;
    int frameRate = 25;
    encoder = avcodec_find_encoder(AV_CODEC_ID_H264);
    codecCtx = initCodecCtx(width, height, fmt, bitRate, frameRate);
    tempFrame = av_frame_alloc();
    scaledFrame = av_frame_alloc();
    tempFrame.pts(-1);
    packet = av_packet_alloc();
}

private AVCodecContext initCodecCtx(int width, int height,int pixFmt, int bitRate, int frameRate) {
    AVCodecContext codec_ctx = avcodec_alloc_context3(encoder);
    codec_ctx.codec_id(AV_CODEC_ID_H264);
    codec_ctx.pix_fmt(pixFmt);
    codec_ctx.width(width);
    codec_ctx.height(height);
    codec_ctx.bit_rate(bitRate);
    codec_ctx.rc_buffer_size(bitRate);
    codec_ctx.framerate().num(frameRate);
    codec_ctx.framerate().den(1);
    codec_ctx.gop_size(frameRate);//每秒1个关键帧
    codec_ctx.time_base().num(1);
    codec_ctx.time_base().den(frameRate);
    codec_ctx.has_b_frames(0);
    codec_ctx.global_quality(1);
    codec_ctx.max_b_frames(0);
    av_opt_set(codec_ctx.priv_data(), "tune", "zerolatency", 0);
    av_opt_set(codec_ctx.priv_data(), "preset", "ultrafast", 0);
    int ret = avcodec_open2(codec_ctx, encoder, (AVDictionary) null);
    return ret == 0 ? codec_ctx : null;
}

把摄像头数据送进来编码,由于摄像头获取到的数据格式和视频编码需要的数据格式往往不一样,所以,编码前需要调用sws_scale对图像数据进行格式转换。

public int recordFrame(Frame frame) {
    byte[] data = frame.data;    // 对应onPreviewFrame回调里的data
    int pf = frame.pixelFormat;  
    if (tempFrameDataLen < data.length) {
        if (tempFrameData != null) {
            tempFrameData.releaseReference();
        }
        tempFrameData = new BytePointer(data.length);
        tempFrameDataLen = data.length;
    }
    tempFrameData.put(data);
    int width = frame.width;
    int height = frame.height;
    av_image_fill_arrays(tempFrame.data(), tempFrame.linesize(), tempFrameData, pf, width, height, frame.align);
    tempFrame.format(pf);
    tempFrame.width(width);
    tempFrame.height(height);
    tempFrame.pts(tempFrame.pts() + 1);
    return recordFrame(tempFrame);
}

public int recordFrame(AVFrame frame) {
    int res = 0;
    int srcFmt = frame.format();
    int dstFmt = codecCtx.pix_fmt();
    int width = frame.width();
    int height = frame.height();
    if (srcFmt != dstFmt) {
        // 图像数据格式转换
        convertCtx = sws_getCachedContext(
                convertCtx,
                width, height, srcFmt,
                width, height, dstFmt,
                SWS_BILINEAR, null, null, (DoublePointer) null
        );
        int requiredDataLen = width * height * 3 / 2;
        if (scaledFrameDataLen < requiredDataLen) {
            if (scaledFrameData != null) {
                scaledFrameData.releaseReference();
            }
            scaledFrameData = new BytePointer(requiredDataLen);
            scaledFrameDataLen = requiredDataLen;
        }
        av_image_fill_arrays(scaledFrame.data(), scaledFrame.linesize(), scaledFrameData, dstFmt, width, height, 1);
        scaledFrame.format(dstFmt);
        scaledFrame.width(width);
        scaledFrame.height(height);
        scaledFrame.pts(frame.pts());
        res = sws_scale(convertCtx, frame.data(), frame.linesize(), 0, height, scaledFrame.data(), scaledFrame.linesize());
        if (res == 0) {
            throw new RuntimeException("scale frame failed");
        }
        frame = scaledFrame;
    }
    res = avcodec_send_frame(codecCtx, frame);
    scaledFrame.pts(scaledFrame.pts() + 1);
    if (res != 0 && res != AVERROR_EAGAIN()) {
        throw new RuntimeException("Failed to encode frame:" + res);
    }
    res = avcodec_receive_packet(codecCtx, packet);
    if (res != 0 && res != AVERROR_EAGAIN()) {
        return res;
    }
    return res;
}

编码完一帧图像后,需要检查是否有AVPacket生成,如果有,把它回写给请求端即可。

AVPacket pkg = encoder.getPacket();
if (outBuffer == null || outBuffer.length < pkg.size()) {
    outBuffer = new byte[pkg.size()];
}
BytePointer pkgData = pkg.data();
if (pkgData == null) {
    return;
}
pkgData.get(outBuffer, 0, pkg.size());
os.write(outBuffer, 0, pkg.size());

重点流程的代码都写好了,把它们连接起来就可以收工了。

收尾

请求端还没写好,先在电脑端使用ffplay测试一下。

ffplay tcp://手机IP:6666

嗯,一切正常!就是延时有点大,主要是ffplay不知道视频流的格式,所以缓冲了很多帧的数据来侦测视频格式,造成了较大的延时。后面有时间,再写篇使用ffmpeg api实时解码H264的文章(🐶

完整项目代码:https://github.com/kasonyang/net-camera文章来源地址https://www.toymoban.com/news/detail-529474.html

到了这里,关于Android实时获取摄像头画面传输至PC端的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 在Orangepi5开发板3588s使用opencv获取摄像头画面

    先感谢香橙派群的管理员耐心指导,经过不断的调试修改最后成功通过opencv调用mipi摄像头获取画面 就记录分享一下大概步骤希望大家少踩点坑!!!!!! 我用的固件系统是 ubuntu2022.0.4 固件是: Orangepi5_1.1.6_ubuntu_jammy_desktop_gnome_linux5 .10.110.img 想通过opencv获取画面得先安装

    2024年02月12日
    浏览(35)
  • Android中相机(Camera)画面旋转角度分析:手机摄像头的“正向”、手机画面自然方向、相机画面的偏转角度

    #.概述:     1.如同人眼看东西分上下一样,摄像头也有其“正向”,正常情况下,Android手机后置、前置摄像头的“正向”朝向为手机的“右侧”(默认如此,除非手机厂商修改设置)。 (这里运行代码做过测试发现,前置摄像头也是以右侧为正向,而不是有些资料上说的

    2024年02月14日
    浏览(44)
  • 解决OpenCV使用USB摄像头获取高分辨率视频低帧率、卡、果冻画面的问题

    最近制作移动设备,需要安置多个usb摄像头到电脑,踩了几个坑,记录一下解决方案。 不能使用分线器插多个usb摄像头,同一时间只能加载一个。 usb摄像头调高分辨率时,非常卡顿,视频画面像果冻一样,及不流畅 第二个情况具体描述: 摄像头支持1920*1080  60fps,opencv在

    2024年02月16日
    浏览(51)
  • OpenCV获取网络摄像头实时视频流

    参考文章: [常用工具] OpenCV获取网络摄像头实时视频流_opencv网络摄像头 [常用工具] OpenCV获取网络摄像头实时视频流_opencv网络摄像头_落痕的寒假的博客-CSDN博客 在使用OpenCv处理视频时,无论是视频文件还是摄像头画面,都要使用VideoCapture类来进行每一帧图像的处理。当我们

    2024年02月01日
    浏览(28)
  • Java获取实时摄像头进行拍照(附源码)

    Java是一种通用编程语言,可以用来开发各种类型的应用程序,包括涉及图像处理和相机操作的应用程序。         要在Java中获取实时摄像头进行拍照,通常会借助一些 第三方库或API ,例如 OpenCV(Open Source Computer Vision Library) 或 Java Media Framework(JMF) 等。这些库和API提

    2024年01月25日
    浏览(28)
  • 小白教程:PyQt5点击按钮,调用笔记本电脑摄像头将实时画面显示在label控件上,并且使用UI代码和逻辑代码分开的方式

    能力一般,水平有限,真心欢迎批评指正!以后会不断完善修改,如果对你有帮助欢迎点赞收藏! 小白教程,当我们掌握了一点点的Qt Designer的技能,得意于能够在界面上作画的时候,怎么编写其中的逻辑关系是我们进一步想要了解的,下面介绍了 点击按钮--调用笔记本电脑

    2024年01月17日
    浏览(57)
  • 使用SOCKET搭建linux和window实现实时摄像头传输(linux传输win端使用C++mfc显示)--Win端开发

    配置: Window10 VS2013 opencv249 如果VS和opencv配置不一样,让版本对应 Opencv与VS版本 1.1 MFC项目搭建 通过这些步骤就创建了一个MFC基础项目。 1.2项目属性配置 本项目因为要使用opencv,所以就要配置以下opencv的环境 首先在opencv官网下载opencv,此次使用opencv2.4.9,下载完并且完成安装 接下

    2024年02月10日
    浏览(35)
  • javacv基础02-调用本机摄像头并预览摄像头图像画面视频

    引入架包: 运行效果: 注意: 1、maven依赖后,会导致整个项目工程打包发布后的体积变得十分巨大 原因是ffmpeg和opencv两个依赖默认会把android,ios,linux,macos,windows以及各自不同cpu芯片下,86/64等所有版本的Jar会全部依赖进来,项目打包后体积剧增500M+ 解决方法也比较简单

    2024年02月11日
    浏览(35)
  • 摄像头画面作为电脑桌面背景

    1. 创建文件main.pyw,文件内容 2. 创建文件requirements.txt,文件内容 3. 安装依赖 4. 运行 双击main.pyw 防火布 github下载代码 从上面第3步开始执行

    2024年02月03日
    浏览(32)
  • ☀️将大华摄像头画面接入Unity 【1】配置硬件和初始化摄像头

    目前的设想是后期采用网口供电的形式把画面传出来,所以这边我除了大华摄像头还准备了POE供电交换机,为了方便索性都用大华的了,然后全都连接电脑主机即可。 这边初始化摄像头需要用到大华的Configtool软件,下载地址如下。 全部-浙江大华技术股份有限公司 (dahuatech

    2024年02月21日
    浏览(67)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包