热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

局域网下,实现一键共享屏幕到移动设备

1.问题起因开发需求刚不久开发一款了教育类app,需要实现教师端对学生移动设备进行远程操控,比如对学生平板进行解锁屏,共享电脑屏幕到学生端,监控学生屏幕内容等。网络环境教

1. 问题起因


开发需求

刚不久开发一款了教育类app,需要实现教师端对学生移动设备进行远程操控,比如对学生平板进行解锁屏,共享电脑屏幕到学生端,监控学生屏幕内容等。

网络环境

教师端网线或WIFI接入,iPad和Android Pad通过WIFI接入,确保在一个网段下。

大致功能

graph TB S(Service
教师端) S--一键解锁/锁定屏幕-->C1 S--一键分发文件
ppt/doc/img-->C2 S--屏幕广播-->C3 S--学生抢答-->C4 S--实时监控-->C5 C1(Client1
iPad/Android Pad) C2(Client2
iPad/Android Pad) C3(Client3
iPad/Android Pad) C4(Client4
iPad/Android Pad) C5(Client4
iPad/Android Pad)

2. 实现方案


教师端采用FFmpeg采集屏幕音视频,iOS、Android端使用ijkplayer拉流播放,流传输协议采用RTMP协议,通讯方式采用TCP Socket

通讯实现

局域网内教师端充当服务器发送UDP广播(内容包含本机IP和端口号),iOS、Android端收到UDP广播获取到IP地址和端口后,采用Socket【CocoaAsyncSocket(iOS)、Socket(Android)】与教师端进行TCP连接,建立连接完成后,通过Socket收发消息进行通讯。

3. 技术模块

3.1 Mac下nginx-full搭建


1.Homebrew安装

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

2.安装nginx-full(rtmp)

brew install nginx-full --with-rtmp-module

如果报错 "Error: invalid option: --with-rtmp-module" 先执行下面命令后再试

brew tap denji/nginx

3.查看nginx安装的路径等信息

brew info nginx-full

会显示出配置文件所在路径

The default port has been set in /usr/local/etc/nginx/nginx.conf to 8080 so that
nginx can run without sudo.

4.配置nginx.conf,文件最后空白处直接添加(application live,live随便起名,之后推流对应就可以了)。

rtmp {
    server {
        listen 1935;
        application live {
            live on;
            record off;
        }
    }
}

5.修改保存后重启nginx

nginx -s reload

3.2 安装ffmepg推流


1.安装

brew install ffmpeg

2.推送屏幕流

ffmpeg -f avfoundation -pixel_format uyvy422 -i "1" -f flv rtmp://localhost:1935/live

执行后显示Output地址,rtmp://localhost:1935/live,也就是本机ip,比如rtmp://192.168.1.2:1935/live,Mac电脑可以安装VLC播放器,测试播放。

Output #0, flv, to \'rtmp://localhost:1935/live\':
  Metadata:
    encoder         : Lavf58.20.100
    Stream #0:0: Video: flv1 (flv) ([2][0][0][0] / 0x0002), yuv420p, 2560x1600, q=2-31, 200 kb/s, 1000k fps, 1k tbn, 1000k tbc
    Metadata:
      encoder         : Lavc58.35.100 flv
    Side data:
      cpb: bitrate max/min/avg: 0/0/200000 buffer size: 0 vbv_delay: -1
frame=  241 fps= 27 q=24.8 size=    5368kB time=00:00:08.86 bitrate=4958.5kbits/s speed=   1x     

3.3 IJKPlayer编译


附件:iOS编译后动态库和Android库文件

参考ijkplayer文档说明,在mac下编译即可,不过在编译之前,需要修改一些配置文件。如果想到达首屏秒开,务必看完这些内容再去编译,包括后面讲到的客户端首屏秒开,因为涉及C文件修改,省去之后又要重新编译。

编译之前一定仔细阅读README.md,比如在编译环境和所需文件:

# install homebrew, git, yasm
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew install git
brew install yasm

# add these lines to your ~/.bash_profile or ~/.profile
# export ANDROID_SDK=
# export ANDROID_NDK=

# on Cygwin (unmaintained)
# install git, make, yasm

还有就是他当时的编译环境My Build Environment,这块需要说明一下,尤其是编译安卓的,NDK就直接用r10e,虽然之后的也可以,但是会有编译失败的可能,因为我编译的时候就失败了,更换为作者使用的版本通过。

Common
Mac OS X 10.11.5
Android
NDK r10e
Android Studio 2.1.3
Gradle 2.14.1
iOS
Xcode 7.3 (7D175)
HomeBrew
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
brew install git

README.md 对应有Build iOS和Build Android,编译哪个平台就执行对应的命令。其中默认链接的脚本是 less codec/format for smaller binary size,具体说明看文档,这里我选择的默认配置。

Build iOS编译中,./init-ios.sh命令久一点,中间要下载一些东西,具体内容可以查看脚本文件。比如== pull ffmpeg base ==,明显要好久,除非你当时下载的速度很快。

== pull ffmpeg base ==
Cloning into \'extra/ffmpeg\'...
remote: Enumerating objects: 538907, done.
Receiving objects:  19% (103984/538907), 30.82 MiB | 42.00 KiB/s   

iOS 编译可能会遇到的问题和解决办法

问题一:

./libavutil/arm/asm.S:50:9: error: unknown directive
        .arch armv7-a
        ^
make: *** [libavcodec/arm/aacpsdsp_neon.o] Error 1
make: *** Waiting for unfinished jobs....

解决办法:

修改./compile-ffmpeg.sh文件

将这一行:FF_ALL_ARCHS_IOS8_SDK="armv7 arm64 i386 x86_64"

修改为:FF_ALL_ARCHS_IOS8_SDK="arm64 i386 x86_64"

问题二:

\'openssl/ssl.h\' file not found
#include  ERROR: openssl not found

解决办法:

编译ffmpeg软解码库,这个过程会生成各种架构的ffmpeg,编译ffmpeg前要先compile OpenSSL,对openssl进行编译,如果未执行可能会报错。必须先执行./compile-openssl.sh all

实际编译的确会遇到这些问题,尤其是问题一。

这些问题参考了博客iOS IJKPlayer 项目集成

3.4 iOS Framwork合并


一切顺利完成后运行demo,编译获取动态库,这边我直接使用真机和模拟器合并的动态库,当然你也可以不要合并,直接使用真机动态库。

1.配置Release模式,Edit Scheme —> Run —> info —> Build Configuration —> Release

2.真机和模拟器各自编译

3.Products —> IJKMediaFramework.framework —> Show in Finder

4.终端 cd Products 目录下,执行: lipo -create 真机 模拟器 -output 合并文件

lipo -create Release-iphoneos/IJKMediaFramework.framework/IJKMediaFramework Release-iphonesimulator/IJKMediaFramework.framework/IJKMediaFramework -output IJKMediaFramework

5.合并后的文件替换掉真机framework下的文件,新的IJKMediaFramework.framework就是合并后的动态库,直接拖拽到项目使用

我的Xcode版本 Version 10.3 (10G8)

附件:iOS编译后动态库和Android库文件

3.5 客户端首屏秒开


首屏秒开,需要结合视频清晰度和延迟,采取合适的帧率。客户端取消缓存也可以减少首个关键帧显示时间。具体参考首屏秒开和追幀播放技术。

附上iOS和Android对IJKPlayer设置。

iOS端:


- (IJKFFOptions *)options {
    if (!_options) {
        IJKFFOptions *optiOns= [IJKFFOptions optionsByDefault];
        // Set param
        [options setFormatOptionIntValue:1024 * 16 forKey:@"probsize"];
        [options setFormatOptionIntValue:50000 forKey:@"analyzeduration"];
        [options setPlayerOptionIntValue:0 forKey:@"videotoolbox"];
        [options setCodecOptionIntValue:IJK_AVDISCARD_DEFAULT forKey:@"skip_loop_filter"];
        [options setCodecOptionIntValue:IJK_AVDISCARD_DEFAULT forKey:@"skip_frame"];
        [options setPlayerOptionIntValue:1000 forKey:@"max_cached_duration"];
        [options setPlayerOptionIntValue:1 forKey:@"infbuf"];  // 无限读
        [options setPlayerOptionIntValue:0 forKey:@"packet-buffering"];
        _optiOns= options;
    }
    return _options;
}

Android端:

// 设置播放前的最大探测时间
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzemaxduration", 100L);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probesize", 10240L);
// 每处理一个packet之后刷新io上下文
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "flush_packets", 1L);
// 是否开启预缓冲,一般直播项目会开启,达到秒开的效果,不过带来了播放丢帧卡顿的体验
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", 0L);
// 放前的探测Size,默认是1M, 改小一点会出画面更快
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "probsize", 200);
// 设置播放前的探测时间 1,达到首屏秒开效果
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzeduration", 1);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max_cached_duration", 1000);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "infbuf", 1);
// 无限读
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "max-buffer-size", 0);
// 不额外优化(使能非规范兼容优化,默认值0 )
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "fast", 1);
// 缩短播放的rtmp视频延迟在1s内
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "fflags", "nobuffer");
// 如果是rtsp协议,可以优先用tcp(默认是用udp)
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "rtmp_transport", "tcp");
// 支持硬解 1:开启 O:关闭
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-hevc", 1);
// 跳帧处理,放CPU处理较慢时,进行跳帧处理,保证播放流程,画面和声音同步
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "rtsp_transport", "tcp");
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 1L);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 1);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "http-detect-range-support", 0);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 48L);
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_frame", 0);
// 因为项目中多次调用播放器,有网络视频,resp,本地视频,还有wifi上http视频,所以得清空DNS才能播放WIFI上的视频
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "dns_cache_clear", 1);

编译之前修改f_ffplay.c,该方法明显提高了首屏延迟问题

路径 ijkmedia—> ijkplayer —> ff_ffplay.c

第一个修改的地方:double vp_duration 方法

将此代码

static double vp_duration(VideoState *is, Frame *vp, Frame *nextvp) {
    if (vp->serial == nextvp->serial) {
        double duration = nextvp->pts - vp->pts;
        if (isnan(duration) || duration <= 0 || duration > is->max_frame_duration)
            return vp->duration;
        else
            return duration;
    } else {
        return 0.0;
    }
}

替换为一下代码

static double vp_duration(VideoState *is, Frame *vp, Frame *nextvp) {
     return vp->duration;
}

第二个修改的地方:static int ffplay_video_thread(void *arg) 方法

注释掉下面这一行代码

AVRational frame_rate = av_guess_frame_rate(is->ic, is->video_st, NULL);

将下面这行代码

duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational){frame_rate.den, frame_rate.num}) : 0);

修改为

duration = 0.01;

更改后如下

static int ffplay_video_thread(void *arg)
{
    FFPlayer *ffp = arg;
    VideoState *is = ffp->is;
    AVFrame *frame = av_frame_alloc();
    double pts;
    double duration;
    int ret;
    AVRational tb = is->video_st->time_base;
  	// 注释掉
    // AVRational frame_rate = av_guess_frame_rate(is->ic, is->video_st, NULL);
    int64_t dst_pts = -1;
    int64_t last_dst_pts = -1;
    int retry_convert_image = 0;
    int convert_frame_count = 0;
  
    // ···此处省略很多代码
  	
#endif
						// 这行代码直接修改为 duration = 0.01;
						// duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational){frame_rate.den, frame_rate.num}) : 0);
            duration = 0.01;
            pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);
            ret = queue_picture(ffp, frame, pts, duration, frame->pkt_pos, is->viddec.pkt_serial);
            av_frame_unref(frame);
#if CONFIG_AVFILTER
        }
#endif

        if (ret <0)
            goto the_end;
    }
 the_end:
#if CONFIG_AVFILTER
    avfilter_graph_free(&graph);
#endif
    av_log(NULL, AV_LOG_INFO, "convert image convert_frame_count = %d\n", convert_frame_count);
    av_frame_free(&frame);
    return 0;
}

修改f_ffplay.c参考了博客ijkplayer的一些问题优化记录


推荐阅读
  • 本文介绍了两种在Android设备上获取MAC地址的有效方法,包括通过Wi-Fi连接和使用移动数据流量的情况。第一种方法依赖于Wi-Fi连接来获取MAC地址,而第二种方法则无需Wi-Fi,直接通过网络接口获取。 ... [详细]
  • 请看|差别_Android 6.0 运行时权限处理解析
    请看|差别_Android 6.0 运行时权限处理解析 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 本文详细介绍了如何在Linux系统上安装和配置Smokeping,以实现对网络链路质量的实时监控。通过详细的步骤和必要的依赖包安装,确保用户能够顺利完成部署并优化其网络性能监控。 ... [详细]
  • 本文介绍了一款用于自动化部署 Linux 服务的 Bash 脚本。该脚本不仅涵盖了基本的文件复制和目录创建,还处理了系统服务的配置和启动,确保在多种 Linux 发行版上都能顺利运行。 ... [详细]
  • 解决PHP与MySQL连接时出现500错误的方法
    本文详细探讨了当使用PHP连接MySQL数据库时遇到500内部服务器错误的多种解决方案,提供了详尽的操作步骤和专业建议。无论是初学者还是有经验的开发者,都能从中受益。 ... [详细]
  • 如何配置Unturned服务器及其消息设置
    本文详细介绍了Unturned服务器的配置方法和消息设置技巧,帮助用户了解并优化服务器管理。同时,提供了关于云服务资源操作记录、远程登录设置以及文件传输的相关补充信息。 ... [详细]
  • UNP 第9章:主机名与地址转换
    本章探讨了用于在主机名和数值地址之间进行转换的函数,如gethostbyname和gethostbyaddr。此外,还介绍了getservbyname和getservbyport函数,用于在服务器名和端口号之间进行转换。 ... [详细]
  • 360SRC安全应急响应:从漏洞提交到修复的全过程
    本文详细介绍了360SRC平台处理一起关键安全事件的过程,涵盖从漏洞提交、验证、排查到最终修复的各个环节。通过这一案例,展示了360在安全应急响应方面的专业能力和严谨态度。 ... [详细]
  • 2023年京东Android面试真题解析与经验分享
    本文由一位拥有6年Android开发经验的工程师撰写,详细解析了京东面试中常见的技术问题。涵盖引用传递、Handler机制、ListView优化、多线程控制及ANR处理等核心知识点。 ... [详细]
  • Netflix利用Druid实现高效实时数据分析
    本文探讨了全球领先的在线娱乐公司Netflix如何通过采用Apache Druid,实现了高效的数据采集、处理和实时分析,从而显著提升了用户体验和业务决策的准确性。文章详细介绍了Netflix在系统架构、数据摄取、管理和查询方面的实践,并展示了Druid在大规模数据处理中的卓越性能。 ... [详细]
  • 本文将详细介绍如何在没有显示器的情况下,使用Raspberry Pi Imager为树莓派4B安装操作系统,并进行基本配置,包括设置SSH、WiFi连接以及更新软件源。 ... [详细]
  • Ubuntu GamePack:专为游戏爱好者打造的Linux发行版
    随着Linux系统在游戏领域的应用越来越广泛,许多Linux用户开始寻求在自己的系统上畅玩游戏的方法。UALinux,一家致力于推广GNU/Linux使用的乌克兰公司,推出了基于Ubuntu 16.04的Ubuntu GamePack,旨在为Linux用户提供一个游戏友好型的操作环境。 ... [详细]
  • 智慧城市建设现状及未来趋势
    随着新基建政策的推进及‘十四五’规划的实施,我国正步入以5G、人工智能等先进技术引领的智慧经济新时代。规划强调加速数字化转型,促进数字政府建设,新基建政策亦倡导城市基础设施的全面数字化。本文探讨了智慧城市的发展背景、全球及国内进展、市场规模、架构设计,以及百度、阿里、腾讯、华为等领军企业在该领域的布局策略。 ... [详细]
  • 随着技术的发展,黑客开始利用AI技术在暗网中创建用户的‘数字孪生’,这一现象引起了安全专家的高度关注。 ... [详细]
author-avatar
红太郎的等待_921
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有