<ul id="qxxfc"><fieldset id="qxxfc"><tr id="qxxfc"></tr></fieldset></ul>


      局域網(wǎng)下,實(shí)現(xiàn)一鍵共享屏幕到移動(dòng)設(shè)備

      1. 問題起因

      開發(fā)需求

      剛不久開發(fā)一款了教育類app,需要實(shí)現(xiàn)教師端對(duì)學(xué)生移動(dòng)設(shè)備進(jìn)行遠(yuǎn)程操控,比如對(duì)學(xué)生平板進(jìn)行解鎖屏,共享電腦屏幕到學(xué)生端,監(jiān)控學(xué)生屏幕內(nèi)容等。

      網(wǎng)絡(luò)環(huán)境

      教師端網(wǎng)線或WIFI接入,iPad和Android Pad通過WIFI接入,確保在一個(gè)網(wǎng)段下。

      大致功能
      graph TB S(Service<br/>教師端) S--一鍵解鎖/鎖定屏幕-->C1 S--一鍵分發(fā)文件<br/>ppt/doc/img-->C2
      S--屏幕廣播-->C3 S--學(xué)生搶答-->C4 S--實(shí)時(shí)監(jiān)控-->C5 C1(Client1 <br/>iPad/Android Pad)
      C2(Client2 <br/>iPad/Android Pad) C3(Client3 <br/>iPad/Android Pad) C4(Client4
      <br/>iPad/Android Pad) C5(Client4 <br/>iPad/Android Pad)
      2. 實(shí)現(xiàn)方案

      教師端采用FFmpeg <https://ffmpeg.org/about.html>采集屏幕音視頻,iOS、Android端使用ijkplayer
      <https://github.com/bilibili/ijkplayer>拉流播放,流傳輸協(xié)議采用RTMP協(xié)議,通訊方式采用TCP Socket。

      通訊實(shí)現(xiàn)

      局域網(wǎng)內(nèi)教師端充當(dāng)服務(wù)器發(fā)送UDP廣播(內(nèi)容包含本機(jī)IP和端口號(hào)),iOS、Android端收到UDP廣播獲取到IP地址和端口后,采用Socket【
      CocoaAsyncSocket <https://github.com/robbiehanson/CocoaAsyncSocket>
      (iOS)、Socket(Android)】與教師端進(jìn)行TCP連接,建立連接完成后,通過Socket收發(fā)消息進(jìn)行通訊。

      3. 技術(shù)模塊

      3.1 Mac下nginx-full搭建

      1.Homebrew安裝 <https://brew.sh>
      /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
      3.查看nginx安裝的路徑等信息
      brew info nginx-full
      會(huì)顯示出配置文件所在路徑
      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隨便起名,之后推流對(duì)應(yīng)就可以了)。
      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

      執(zhí)行后顯示Output地址,rtmp://localhost:1935/live,也就是本機(jī)ip,比如rtmp://192.168.1.2:1935/live,Mac電腦可以安裝VLC播放器,測(cè)試播放。
      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編譯后動(dòng)態(tài)庫和Android庫文件 <https://github.com/superxjhw/Document>

      參考ijkplayer <https://github.com/bilibili/ijkplayer>
      文檔說明,在mac下編譯即可,不過在編譯之前,需要修改一些配置文件。如果想到達(dá)首屏秒開,務(wù)必看完這些內(nèi)容再去編譯,包括后面講到的客戶端首屏秒開
      ,因?yàn)樯婕癈文件修改,省去之后又要重新編譯。

      編譯之前一定仔細(xì)閱讀README.md,比如在編譯環(huán)境和所需文件:
      # 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=<your sdk path> # export ANDROID_NDK=<your ndk
      path> # on Cygwin (unmaintained) # install git, make, yasm
      還有就是他當(dāng)時(shí)的編譯環(huán)境My Build
      Environment,這塊需要說明一下,尤其是編譯安卓的,NDK就直接用r10e,雖然之后的也可以,但是會(huì)有編譯失敗的可能,因?yàn)槲揖幾g的時(shí)候就失敗了,更換為作者使用的版本通過。
      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 對(duì)應(yīng)有Build iOS和Build Android,編譯哪個(gè)平臺(tái)就執(zhí)行對(duì)應(yīng)的命令。其中默認(rèn)鏈接的腳本是 less
      codec/format for smaller binary size,具體說明看文檔,這里我選擇的默認(rèn)配置。

      Build iOS編譯中,./init-ios.sh命令久一點(diǎn),中間要下載一些東西,具體內(nèi)容可以查看腳本文件。比如== pull ffmpeg base
      ==,明顯要好久,除非你當(dāng)時(shí)下載的速度很快。
      == 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 編譯可能會(huì)遇到的問題和解決辦法

      問題一:
      ./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 <openssl/ssl.h> ERROR: openssl not
      found
      解決辦法:

      編譯ffmpeg軟解碼庫,這個(gè)過程會(huì)生成各種架構(gòu)的ffmpeg,編譯ffmpeg前要先compile
      OpenSSL,對(duì)openssl進(jìn)行編譯,如果未執(zhí)行可能會(huì)報(bào)錯(cuò)。必須先執(zhí)行./compile-openssl.sh all

      實(shí)際編譯的確會(huì)遇到這些問題,尤其是問題一。

      這些問題參考了博客iOS IJKPlayer 項(xiàng)目集成 <https://www.jianshu.com/p/45c49db8c538>

      3.4 iOS Framwork合并

      一切順利完成后運(yùn)行demo,編譯獲取動(dòng)態(tài)庫,這邊我直接使用真機(jī)和模擬器合并的動(dòng)態(tài)庫,當(dāng)然你也可以不要合并,直接使用真機(jī)動(dòng)態(tài)庫。

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

      2.真機(jī)和模擬器各自編譯

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

      4.終端 cd Products 目錄下,執(zhí)行: lipo -create 真機(jī) 模擬器 -output 合并文件
      lipo -create Release-iphoneos/IJKMediaFramework.framework/IJKMediaFramework
      Release-iphonesimulator/IJKMediaFramework.framework/IJKMediaFramework -output
      IJKMediaFramework
      5.合并后的文件替換掉真機(jī)framework下的文件,新的IJKMediaFramework.framework就是合并后的動(dòng)態(tài)庫,直接拖拽到項(xiàng)目使用

      我的Xcode版本 Version 10.3 (10G8)

      附件:iOS編譯后動(dòng)態(tài)庫和Android庫文件 <https://github.com/superxjhw/Document>

      3.5 客戶端首屏秒開

      首屏秒開,需要結(jié)合視頻清晰度和延遲,采取合適的幀率??蛻舳巳∠彺嬉部梢詼p少首個(gè)關(guān)鍵幀顯示時(shí)間。具體參考首屏秒開和追幀播放技術(shù)
      <https://cloud.baidu.com/doc/LSS/s/djwvyyao9/>。

      附上iOS和Android對(duì)IJKPlayer設(shè)置。

      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端:
      // 設(shè)置播放前的最大探測(cè)時(shí)間 ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT,
      "analyzemaxduration", 100L);
      ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probesize",
      10240L); // 每處理一個(gè)packet之后刷新io上下文
      ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "flush_packets",
      1L); // 是否開啟預(yù)緩沖,一般直播項(xiàng)目會(huì)開啟,達(dá)到秒開的效果,不過帶來了播放丟幀卡頓的體驗(yàn)
      ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER,
      "packet-buffering", 0L); // 放前的探測(cè)Size,默認(rèn)是1M, 改小一點(diǎn)會(huì)出畫面更快
      ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "probsize", 200);
      // 設(shè)置播放前的探測(cè)時(shí)間 1,達(dá)到首屏秒開效果
      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); // 不額外優(yōu)化(使能非規(guī)范兼容優(yōu)化,默認(rèn)值0 )
      ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "fast", 1); //
      縮短播放的rtmp視頻延遲在1s內(nèi) ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT,
      "fflags", "nobuffer"); // 如果是rtsp協(xié)議,可以優(yōu)先用tcp(默認(rèn)是用udp)
      ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "rtmp_transport",
      "tcp"); // 支持硬解 1:開啟 O:關(guān)閉
      ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-hevc",
      1); // 跳幀處理,放CPU處理較慢時(shí),進(jìn)行跳幀處理,保證播放流程,畫面和聲音同步
      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); // 因?yàn)轫?xiàng)目中多次調(diào)用播放器,有網(wǎng)絡(luò)視頻,resp,本地視頻,還有wifi上http視頻,所以得清空DNS才能播放WIFI上的視頻
      ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "dns_cache_clear",
      1);
      編譯之前修改f_ffplay.c,該方法明顯提高了首屏延遲問題

      路徑 ijkmedia—> ijkplayer —> ff_ffplay.c

      第一個(gè)修改的地方: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; }
      第二個(gè)修改的地方: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的一些問題優(yōu)化記錄
      <https://blog.csdn.net/hejjunlin/article/details/57075026>

      友情鏈接
      ioDraw流程圖
      API參考文檔
      OK工具箱
      云服務(wù)器優(yōu)惠
      阿里云優(yōu)惠券
      騰訊云優(yōu)惠券
      京東云優(yōu)惠券
      站點(diǎn)信息
      問題反饋
      郵箱:[email protected]
      QQ群:637538335
      關(guān)注微信

        <ul id="qxxfc"><fieldset id="qxxfc"><tr id="qxxfc"></tr></fieldset></ul>
          我被两个男人躁了一天小说 | 乌克兰bbox | 嗯灬啊灬快灬高潮了 | 日本乱伦一区二区 | 操屄视屏| 一级黄片免费看 | 屄在线| 9118禁| 欧美精品性 | 黄色视频在线观看网站 |