Jack Jiang

          我的最新工程MobileIMSDK:http://git.oschina.net/jackjiang/MobileIMSDK
          posts - 499, comments - 13, trackbacks - 0, articles - 1

          本文由“逆流的魚yuiop”原創分享于“何俊林”公眾號,感謝作者的無私分享。

          1、引言

          直播行業的競爭越來越激烈,進過2018年這波洗牌后,已經度過了蠻荒暴力期,剩下的都是在不斷追求體驗。最近正好在做直播首開優化工作,實踐中通過多種方案并行,已經能把首開降到500ms以下,借此機會分享出來,希望能對大家有所啟發。

          本文內容的技術前提:

          1)基于FFmpeg的ijkplayer,最新版本0.88版本;

          2)拉流協議基于http-flv。

          http-flv更穩定些,國內大部分直播公司基本都是使用http-flv了,從我們實際數據來看,http-flv確實稍微更快些。但是考慮到會有rtmp源,這塊也加了些優化。

          (本文同步發布于:http://www.52im.net/thread-2087-1-1.html

          2、相關文章

          淺談開發實時視頻直播平臺的技術要點

          實現延遲低于500毫秒的1080P實時音視頻直播的實踐分享

          移動端實時視頻直播技術實踐:如何做到實時秒開、流暢不卡

          技術揭秘:支持百萬級粉絲互動的Facebook實時視頻直播

          移動端實時音視頻直播技術詳解(一):開篇

          移動端實時音視頻直播技術詳解(二):采集

          移動端實時音視頻直播技術詳解(三):處理

          移動端實時音視頻直播技術詳解(四):編碼和封裝

          移動端實時音視頻直播技術詳解(五):推流和傳輸

          移動端實時音視頻直播技術詳解(六):延遲優化

          理論聯系實際:實現一個簡單地基于HTML5的實時視頻直播

          淺談實時音視頻直播中直接影響用戶體驗的幾項關鍵技術指標

          首次披露:快手是如何做到百萬觀眾同場看直播仍能秒開且不卡頓的?

          Android直播入門實踐:動手搭建一套簡單的直播系統

          網易云信實時視頻直播在TCP數據傳輸層的一些優化思路

          P2P技術如何將實時視頻直播帶寬降低75%?

          近期大熱的實時直播答題系統的實現思路與技術難點分享

          七牛云技術分享:使用QUIC協議實現實時視頻直播0卡頓!

          實時視頻直播客戶端技術盤點:Native、HTML5、WebRTC、微信小程序

          實時音頻的混音在視頻直播應用中的技術原理和實踐總結

          新浪微博技術分享:微博實時直播答題的百萬高并發架構實踐

          3、IP直通車

          簡單理解就是,把域名替換成IP。比如https://www.baidu.com/,你可以直接換成14.215.177.39,這樣做的目的是,省去了DNS解析的耗時,尤其在網絡不好時,訪問域名,域名要去解析,再給你返回。不僅僅有時間解析過長的問題,還有小運營商DNS劫持的問題。一般就是在啟動應用時,就開始對拉流的域名進行預解析好,存到本地,然后在真正拉流時,直接用就行。典型的案列,就是很多人使用HTTPDNS,這個github上也有開源,可以自行去研究下。

          需要注意的是:這種方案在使用 HTTPS 時,是會失敗的。因為 HTTPS 在證書驗證的過程,會出現 domain 不匹配導致 SSL/TLS 握手不成功。

          4、服務端 GOP 緩存優化

          除了客戶端業務側的優化外,我們還可以從流媒體服務器側進行優化。

          我們都知道直播流中的圖像幀分為:I 幀、P 幀、B 幀,其中只有 I 幀是能不依賴其他幀獨立完成解碼的,這就意味著當播放器接收到 I 幀它能馬上渲染出來,而接收到 P 幀、B 幀則需要等待依賴的幀而不能立即完成解碼和渲染,這個期間就是「黑屏」了。

          所以,在服務器端可以通過緩存 GOP(在 H.264 中,GOP 是封閉的,是以 I 幀開頭的一組圖像幀序列),保證播放端在接入直播時能先獲取到 I 幀馬上渲染出畫面來,從而優化首屏加載的體驗。

          這里有一個 IDR 幀的概念需要講一下,所有的 IDR 幀都是 I 幀,但是并不是所有 I 幀都是 IDR 幀,IDR 幀是 I 幀的子集。

          I 幀嚴格定義是幀內編碼幀,由于是一個全幀壓縮編碼幀,通常用 I 幀表示「關鍵幀」。IDR 是基于 I 幀的一個擴展,帶了控制邏輯,IDR 圖像都是 I 幀圖像,當解碼器解碼到 IDR 圖像時,會立即將參考幀隊列清空,將已解碼的數據全部輸出或拋棄。重新查找參數集,開始一個新的序列。這樣如果前一個序列出現重大錯誤,在這里可以獲得重新同步的機會。IDR 圖像之后的圖像永遠不會使用 IDR 之前的圖像的數據來解碼。

          在 H.264 編碼中,GOP 是封閉式的,一個 GOP 的第一幀都是 IDR 幀。

          5、推流端設置和優化

          一般播放器需要拿到一個完整的GOP,才能記性播放。GOP是在推流端可以設置,比如下面這個圖,是我dump一個流,看到的GOP情況。GOP大小是50,推流過來的fps設置是25,也就是1s內會顯示25個Frame,50個Frame,剛好直播設置GOP 2S,但是直播一般fps不用設置這么高,可以隨便dump任何一家直播公司的推流,設置fps在15-18之間就夠了。

          6、客戶端播放器的相關耗時和優化

          當set一個源給播放器后,播放器需要open這個流,然后和服務端建立長連接,然后demux,codec,最后渲染。

          我們可以按照播放器的四大塊,依次優化:

          1)數據請求耗時;

          2)解復用耗時;

          3)解碼耗時;

          4)渲染出圖耗時

          6.1 數據請求

          這里就是網絡和協議相關。無論是http-flv,還是rtmp,都主要是基于tcp的,所以一定會有tcp三次握手,同時打開tcp.c分析。需要加日志在一些方法中,如下tcp_open方法。

          下面是已經改動過的代碼:

          /* return non zero if error */

          staticinttcp_open(URLContext *h, constchar*uri, intflags)

          {

              av_log(NULL, AV_LOG_INFO, "tcp_open begin");

              ...省略部分代碼

              if(!dns_entry) {

          #ifdef HAVE_PTHREADS

                  av_log(h, AV_LOG_INFO, "ijk_tcp_getaddrinfo_nonblock begin.\n");

                  ret = ijk_tcp_getaddrinfo_nonblock(hostname, portstr, &hints, &ai, s->addrinfo_timeout, &h->interrupt_callback, s->addrinfo_one_by_one);

                  av_log(h, AV_LOG_INFO, "ijk_tcp_getaddrinfo_nonblock end.\n");

          #else

                  if(s->addrinfo_timeout > 0)

                      av_log(h, AV_LOG_WARNING, "Ignore addrinfo_timeout without pthreads support.\n");

                  av_log(h, AV_LOG_INFO, "getaddrinfo begin.\n");

                  if(!hostname[0])

                      ret = getaddrinfo(NULL, portstr, &hints, &ai);

                  else

                      ret = getaddrinfo(hostname, portstr, &hints, &ai);

                  av_log(h, AV_LOG_INFO, "getaddrinfo end.\n");

          #endif


                  if(ret) {

                      av_log(h, AV_LOG_ERROR,

                          "Failed to resolve hostname %s: %s\n",

                          hostname, gai_strerror(ret));

                      returnAVERROR(EIO);

                  }


                  cur_ai = ai;

              } else{

                  av_log(NULL, AV_LOG_INFO, "Hit DNS cache hostname = %s\n", hostname);

                  cur_ai = dns_entry->res;

              }


           restart:

          #if HAVE_STRUCT_SOCKADDR_IN6

              // workaround for IOS9 getaddrinfo in IPv6 only network use hardcode IPv4 address can not resolve port number.

              if(cur_ai->ai_family == AF_INET6){

                  structsockaddr_in6 * sockaddr_v6 = (structsockaddr_in6 *)cur_ai->ai_addr;

                  if(!sockaddr_v6->sin6_port){

                      sockaddr_v6->sin6_port = htons(port);

                  }

              }

          #endif


              fd = ff_socket(cur_ai->ai_family,

                             cur_ai->ai_socktype,

                             cur_ai->ai_protocol);

              if(fd < 0) {

                  ret = ff_neterrno();

                  gotofail;

              }


              /* Set the socket's send or receive buffer sizes, if specified.

                 If unspecified or setting fails, system default is used. */

              if(s->recv_buffer_size > 0) {

                  setsockopt (fd, SOL_SOCKET, SO_RCVBUF, &s->recv_buffer_size, sizeof(s->recv_buffer_size));

              }

              if(s->send_buffer_size > 0) {

                  setsockopt (fd, SOL_SOCKET, SO_SNDBUF, &s->send_buffer_size, sizeof(s->send_buffer_size));

              }


              if(s->listen == 2) {

                  // multi-client

                  if((ret = ff_listen(fd, cur_ai->ai_addr, cur_ai->ai_addrlen)) < 0)

                      gotofail1;

              } elseif(s->listen == 1) {

                  // single client

                  if((ret = ff_listen_bind(fd, cur_ai->ai_addr, cur_ai->ai_addrlen,

                                            s->listen_timeout, h)) < 0)

                      gotofail1;

                  // Socket descriptor already closed here. Safe to overwrite to client one.

                  fd = ret;

              } else{

                  ret = av_application_on_tcp_will_open(s->app_ctx);

                  if(ret) {

                      av_log(NULL, AV_LOG_WARNING, "terminated by application in AVAPP_CTRL_WILL_TCP_OPEN");

                      gotofail1;

                  }


                  if((ret = ff_listen_connect(fd, cur_ai->ai_addr, cur_ai->ai_addrlen,

                                               s->open_timeout / 1000, h, !!cur_ai->ai_next)) < 0) {

                      if(av_application_on_tcp_did_open(s->app_ctx, ret, fd, &control))

                          gotofail1;

                      if(ret == AVERROR_EXIT)

                          gotofail1;

                      else

                          gotofail;

                  } else{

                      ret = av_application_on_tcp_did_open(s->app_ctx, 0, fd, &control);

                      if(ret) {

                          av_log(NULL, AV_LOG_WARNING, "terminated by application in AVAPP_CTRL_DID_TCP_OPEN");

                          gotofail1;

                      } elseif(!dns_entry && strcmp(control.ip, hostname_bak)) {

                          add_dns_cache_entry(hostname_bak, cur_ai, s->dns_cache_timeout);

                          av_log(NULL, AV_LOG_INFO, "Add dns cache hostname = %s, ip = %s\n", hostname_bak , control.ip);

                      }

                  }

              }


              h->is_streamed = 1;

              s->fd = fd;


              if(dns_entry) {

                  release_dns_cache_reference(hostname_bak, &dns_entry);

              } else{

                  freeaddrinfo(ai);

              }

              av_log(NULL, AV_LOG_INFO, "tcp_open end");

              return0;

              // 省略部分代碼

          }

          改動地方主要是hints.ai_family = AF_INET;,原來是    hints.ai_family = AF_UNSPEC;,原來設計是一個兼容IPv4和IPv6的配置,如果修改成AF_INET,那么就不會有AAAA的查詢包了。如果只有IPv4的請求,就可以改成AF_INET。當然有IPv6,這里就不要動了。這么看是否有,可以通過抓包工具看。

          接著分析,我們發現tcp_read函數是個阻塞式的,會非常耗時,我們又不能設置短一點中斷時間,因為短了的話,造成讀取不到數據,就中斷,后續播放就直接失敗了,這里只能讓它等。

          不過還是優化的點是下面部分:

          static int tcp_read(URLContext *h, uint8_t *buf, intsize)

          {

              av_log(NULL, AV_LOG_INFO, "tcp_read begin %d\n", size);

              TCPContext *s = h->priv_data;

              intret;


              if(!(h->flags & AVIO_FLAG_NONBLOCK)) {

                  ret = ff_network_wait_fd_timeout(s->fd, 0, h->rw_timeout, &h->interrupt_callback);

                  if(ret)

                      return ret;

              }

              ret = recv(s->fd, buf, size, 0);

              if(ret == 0)

                  returnAVERROR_EOF;

              //if (ret > 0)

              //    av_application_did_io_tcp_read(s->app_ctx, (void*)h, ret);

              av_log(NULL, AV_LOG_INFO, "tcp_read end %d\n", ret);

              returnret < 0 ? ff_neterrno() : ret;

          }

          我們可以把上面兩行注釋掉,因為在ff_network_wait_fd_timeout等回來后,數據可以放到buf中,下面av_application_did_io_tcp_read就沒必要去執行了。原來每次ret>0,都會執行av_application_did_io_tcp_read這個函數。

          6.2 解復用耗時

          在日志中發現,數據請求到后,進行音視頻分離時,首先需要匹配對應demuxer,其中ffmpeg的av_find_input_format和avformat_find_stream_info非常耗時,前者簡單理解就是打開某中請求到數據,后者就是探測流的一些信息,做一些樣本檢測,讀取一定長度的碼流數據,來分析碼流的基本信息,為視頻中各個媒體流的 AVStream 結構體填充好相應的數據。這個函數中做了查找合適的解碼器、打開解碼器、讀取一定的音視頻幀數據、嘗試解碼音視頻幀等工作,基本上完成了解碼的整個流程。這時一個同步調用,在不清楚視頻數據的格式又要做到較好的兼容性時,這個過程是比較耗時的,從而會影響到播放器首屏秒開。

          這兩個函數調用都在ff_ffplay.c的read_thread函數中:

          if(ffp->iformat_name) {

                  av_log(ffp, AV_LOG_INFO, "av_find_input_format noraml begin");

                  is->iformat = av_find_input_format(ffp->iformat_name);

                  av_log(ffp, AV_LOG_INFO, "av_find_input_format normal end");

              }

              elseif(av_stristart(is->filename, "rtmp", NULL)) {

                  av_log(ffp, AV_LOG_INFO, "av_find_input_format rtmp begin");

                  is->iformat = av_find_input_format("flv");

                  av_log(ffp, AV_LOG_INFO, "av_find_input_format rtmp end");

                  ic->probesize = 4096;

                  ic->max_analyze_duration = 2000000;

                  ic->flags |= AVFMT_FLAG_NOBUFFER;

              }

              av_log(ffp, AV_LOG_INFO, "avformat_open_input begin");

              err = avformat_open_input(&ic, is->filename, is->iformat, &ffp->format_opts);

              av_log(ffp, AV_LOG_INFO, "avformat_open_input end");

              if(err < 0) {

                  print_error(is->filename, err);

                  ret = -1;

                  gotofail;

              }

              ffp_notify_msg1(ffp, FFP_MSG_OPEN_INPUT);


              if(scan_all_pmts_set)

                  av_dict_set(&ffp->format_opts, "scan_all_pmts", NULL, AV_DICT_MATCH_CASE);


              if((t = av_dict_get(ffp->format_opts, "", NULL, AV_DICT_IGNORE_SUFFIX))) {

                  av_log(NULL, AV_LOG_ERROR, "Option %s not found.\n", t->key);

          #ifdef FFP_MERGE

                  ret = AVERROR_OPTION_NOT_FOUND;

                  gotofail;

          #endif

              }

              is->ic = ic;


              if(ffp->genpts)

                  ic->flags |= AVFMT_FLAG_GENPTS;


              av_format_inject_global_side_data(ic);


              if(ffp->find_stream_info) {

                  AVDictionary **opts = setup_find_stream_info_opts(ic, ffp->codec_opts);

                  intorig_nb_streams = ic->nb_streams;


                  do{

                      if(av_stristart(is->filename, "data:", NULL) && orig_nb_streams > 0) {

                          for(i = 0; i < orig_nb_streams; i++) {

                              if(!ic->streams[i] || !ic->streams[i]->codecpar || ic->streams[i]->codecpar->profile == FF_PROFILE_UNKNOWN) {

                                  break;

                              }

                          }


                          if(i == orig_nb_streams) {

                              break;

                          }

                      }

                      ic->probesize=100*1024;

                      ic->max_analyze_duration=5*AV_TIME_BASE;

                      ic->fps_probe_size=0;

                      av_log(ffp, AV_LOG_INFO, "avformat_find_stream_info begin");

                      err = avformat_find_stream_info(ic, opts);

                      av_log(ffp, AV_LOG_INFO, "avformat_find_stream_info end");

                  } while(0);

                  ffp_notify_msg1(ffp, FFP_MSG_FIND_STREAM_INFO);

          最終改的如上,主要是對rtmp增加了,指定format為‘flv’,以及樣本大小。

          同時在外部可以通過設置 probesize 和 analyzeduration 兩個參數來控制該函數讀取的數據量大小和分析時長為比較小的值來降低 avformat_find_stream_info的耗時,從而優化播放器首屏秒開。但是,需要注意的是這兩個參數設置過小時,可能會造成預讀數據不足,無法解析出碼流信息,從而導致播放失敗、無音頻或無視頻的情況。所以,在服務端對視頻格式進行標準化轉碼,從而確定視頻格式,進而再去推算 avformat_find_stream_info分析碼流信息所兼容的最小的probesize和 analyzeduration,就能在保證播放成功率的情況下最大限度地區優化首屏秒開。

          在 FFmpeg 中的 utils.c 文件中的函數實現中有一行代碼是 int fps_analyze_framecount = 20;,這行代碼的大概用處是,如果外部沒有額外設置這個值,那么 avformat_find_stream_info 需要獲取至少 20 幀視頻數據,這對于首屏來說耗時就比較長了,一般都要 1s 左右。而且直播還有實時性的需求,所以沒必要至少取 20 幀。

          將這個值初始化為2,看看效果:

          /* check if one codec still needs to be handled */

                  for(i = 0; i < ic->nb_streams; i++) {

                      intfps_analyze_framecount = 2;


                      st = ic->streams[i];

                      if(!has_codec_parameters(st, NULL))

                          break;


                      if(ic->metadata) {

                          AVDictionaryEntry *t = av_dict_get(ic->metadata, "skip-calc-frame-rate", NULL, AV_DICT_MATCH_CASE);

                          if(t) {

                              intfps_flag = (int) strtol(t->value, NULL, 10);

                              if(!st->r_frame_rate.num && st->avg_frame_rate.num > 0 && st->avg_frame_rate.den > 0 && fps_flag > 0) {

                                  intavg_fps = st->avg_frame_rate.num / st->avg_frame_rate.den;

                                  if(avg_fps > 0 && avg_fps <= 120) {

                                      st->r_frame_rate.num = st->avg_frame_rate.num;

                                      st->r_frame_rate.den = st->avg_frame_rate.den;

                                  }

                              }

                          }

                      }

          這樣,avformat_find_stream_info 的耗時就可以縮減到 100ms 以內。

          6.3 解碼耗時和渲染出圖耗時

          最后就是解碼耗時和渲染出圖耗時,這塊優化空間很少,大頭都在前面。

          有人開始拋出問題了,你這個起播快是快,但是后面網絡不好,卡頓怎么辦?直播中會引起卡頓,主要是網絡有抖動的時候,沒有足夠的數據來播放,ijkplayer會激發其緩沖機制。

          主要是有幾個宏控制:

          DEFAULT_FIRST_HIGH_WATER_MARK_IN_MS:網絡差時首次去喚醒read_thread函數去讀取數據。

          DEFAULT_NEXT_HIGH_WATER_MARK_IN_MS:第二次去喚醒read_thread函數去讀取數據。

          DEFAULT_LAST_HIGH_WATER_MARK_IN_MS這個宏的意思是最后的機會去喚醒read_thread函數去讀取數據。

          可以設置DEFAULT_LAST_HIGH_WATER_MARK_IN_MS為1 * 1000,也即緩沖1秒后開始通知緩沖完成去讀取數據,默認是5秒,如果過大,會讓用戶等太久,那么每次讀取的bytes也可以少些。可以設置DEFAULT_HIGH_WATER_MARK_IN_BYTES小一些,設置為30 * 1024,默認是256 * 1024。

          把BUFFERING_CHECK_PER_MILLISECONDS設置為50,默認是500:

          #define DEFAULT_HIGH_WATER_MARK_IN_BYTES        (30 * 1024)

          #define DEFAULT_FIRST_HIGH_WATER_MARK_IN_MS     (100)

          #define DEFAULT_NEXT_HIGH_WATER_MARK_IN_MS      (1 * 1000)

          #define DEFAULT_LAST_HIGH_WATER_MARK_IN_MS      (1 * 1000)

          #define BUFFERING_CHECK_PER_BYTES               (512)

          #define BUFFERING_CHECK_PER_MILLISECONDS        (50)

          可以看下這些宏使用的地方:

          inline static void ffp_reset_demux_cache_control(FFDemuxCacheControl *dcc)

          {

              dcc->min_frames                = DEFAULT_MIN_FRAMES;

              dcc->max_buffer_size           = MAX_QUEUE_SIZE;

              dcc->high_water_mark_in_bytes  = DEFAULT_HIGH_WATER_MARK_IN_BYTES;


              dcc->first_high_water_mark_in_ms    = DEFAULT_FIRST_HIGH_WATER_MARK_IN_MS;

              dcc->next_high_water_mark_in_ms     = DEFAULT_NEXT_HIGH_WATER_MARK_IN_MS;

              dcc->last_high_water_mark_in_ms     = DEFAULT_LAST_HIGH_WATER_MARK_IN_MS;

              dcc->current_high_water_mark_in_ms  = DEFAULT_FIRST_HIGH_WATER_MARK_IN_MS;

          }

          最后優化的點,是設置一些參數值,也能優化一部分,實際上很多直播用軟件用低分辨率240,甚至360,來達到秒開,可以可以作為一個減少耗時點來展開的,因為分辨率越低,數據量越少,首開越快。

          mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "opensles", 0);

          mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 1);

          mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 1);


          mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "http-detect-range-support", 0);

          mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "fflags", "nobuffer");

          mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "flush_packets", 1);

          mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "max_delay", 0);


          mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 48);


          mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "packet-buffering", 0);

          mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "max-buffer-size", 4* 1024);

          mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "min-frames", 50);

          mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probsize", "1024");

          mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzeduration", "100");

          mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "dns_cache_clear", 1);

          //靜音

          //mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "an", 1);

          //重連模式,如果中途服務器斷開了連接,讓它重新連接

          mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "reconnect", 1);

          6.4 測試數據

          以上完了后,就可以看下測試數據,分辨率在540p以下基本秒開,在4G網絡下測試:

          1)河北衛視直播源,測試10組,平均下來300ms。一組數據386ms,如下:

          11-17 14:17:46.659  9896 10147 D IJKMEDIA: IjkMediaPlayer_native_setup

          11-17 14:17:46.663  9896 10147 V IJKMEDIA: setDataSource: path [url=http://weblive.hebtv.com/live/hbws_bq/index.m3u8]http://weblive.hebtv.com/live/hbws_bq/index.m3u8[/url]

          11-17 14:17:46.666  9896 10177 I FFMPEG  : [FFPlayer @ 0xe070d400] avformat_open_input begin

          11-17 14:17:46.841  9896 10177 I FFMPEG  : [FFPlayer @ 0xe070d400] avformat_open_input end

          11-17 14:17:46.841  9896 10177 I FFMPEG  : [FFPlayer @ 0xe070d400] avformat_find_stream_info begin

          11-17 14:17:46.894  9896 10177 I FFMPEG  : [FFPlayer @ 0xe070d400] avformat_find_stream_info end

          11-17 14:17:47.045  9896 10191 D IJKMEDIA: Video: first frame decoded

          11-17 14:17:47.046  9896 10175 D IJKMEDIA: FFP_MSG_VIDEO_DECODED_START:

          2)映客直播秀場源,測試10組,平均下來400ms。一組數據418ms,如下:

          11-17 14:21:32.908 11464 11788 D IJKMEDIA: IjkMediaPlayer_native_setup

          11-17 14:21:32.952 11464 11788 V IJKMEDIA: setDataSource: path [flash]http://14.215.100.45/hw.pull.inke.cn/live/1542433669916866_0_ud.flv[/flash]?ikDnsOp=1001&ikHost=hw&ikOp=0&codecInfo=8192&ikLog=1&ikSyncBeta=1&dpSrc=6&push_host=trans.push.cls.inke.cn&ikMinBuf=2900&ikMaxBuf=3600&ikSlowRate=0.9&ikFastRate=1.1

          11-17 14:21:32.996 11464 11818 I FFMPEG  : [FFPlayer @ 0xc2575c00] avformat_open_input begin

          11-17 14:21:33.161 11464 11818 I FFMPEG  : [FFPlayer @ 0xc2575c00] avformat_open_input end

          11-17 14:21:33.326 11464 11829 D IJKMEDIA: Video: first frame decoded

          3)熊貓直播游戲直播源,測試10組,平均下來350ms。一組數據373ms,如下:

          11-17 14:29:17.615 15801 16053 D IJKMEDIA: IjkMediaPlayer_native_setup

          11-17 14:29:17.645 15801 16053 V IJKMEDIA: setDataSource: path [flash]http://flv-live-qn.xingxiu.panda.tv/panda-xingxiu/dc7eb0c2e78c96646591aae3a20b0686.flv[/flash]

          11-17 14:29:17.649 15801 16079 I FFMPEG  : [FFPlayer @ 0xeb5ef000] avformat_open_input begin

          11-17 14:29:17.731 15801 16079 I FFMPEG  : [FFPlayer @ 0xeb5ef000] avformat_open_input end

          11-17 14:29:17.988 15801 16090 D IJKMEDIA: Video: first frame decoded

          附錄:更多音視頻技術文章

          [1] 實時音視頻開發的其它精華資料:

          即時通訊音視頻開發(一):視頻編解碼之理論概述

          即時通訊音視頻開發(二):視頻編解碼之數字視頻介紹

          即時通訊音視頻開發(三):視頻編解碼之編碼基礎

          即時通訊音視頻開發(四):視頻編解碼之預測技術介紹

          即時通訊音視頻開發(五):認識主流視頻編碼技術H.264

          即時通訊音視頻開發(六):如何開始音頻編解碼技術的學習

          即時通訊音視頻開發(七):音頻基礎及編碼原理入門

          即時通訊音視頻開發(八):常見的實時語音通訊編碼標準

          即時通訊音視頻開發(九):實時語音通訊的回音及回音消除概述

          即時通訊音視頻開發(十):實時語音通訊的回音消除技術詳解

          即時通訊音視頻開發(十一):實時語音通訊丟包補償技術詳解

          即時通訊音視頻開發(十二):多人實時音視頻聊天架構探討

          即時通訊音視頻開發(十三):實時視頻編碼H.264的特點與優勢

          即時通訊音視頻開發(十四):實時音視頻數據傳輸協議介紹

          即時通訊音視頻開發(十五):聊聊P2P與實時音視頻的應用情況

          即時通訊音視頻開發(十六):移動端實時音視頻開發的幾個建議

          即時通訊音視頻開發(十七):視頻編碼H.264、VP8的前世今生

          實時語音聊天中的音頻處理與編碼壓縮技術簡述

          網易視頻云技術分享:音頻處理與壓縮技術快速入門

          學習RFC3550:RTP/RTCP實時傳輸協議基礎知識

          基于RTMP數據傳輸協議的實時流媒體技術研究(論文全文)

          聲網架構師談實時音視頻云的實現難點(視頻采訪)

          淺談開發實時視頻直播平臺的技術要點

          還在靠“喂喂喂”測試實時語音通話質量?本文教你科學的評測方法!

          實現延遲低于500毫秒的1080P實時音視頻直播的實踐分享

          移動端實時視頻直播技術實踐:如何做到實時秒開、流暢不卡

          如何用最簡單的方法測試你的實時音視頻方案

          技術揭秘:支持百萬級粉絲互動的Facebook實時視頻直播

          簡述實時音視頻聊天中端到端加密(E2EE)的工作原理

          理論聯系實際:實現一個簡單地基于HTML5的實時視頻直播

          IM實時音視頻聊天時的回聲消除技術詳解

          淺談實時音視頻直播中直接影響用戶體驗的幾項關鍵技術指標

          如何優化傳輸機制來實現實時音視頻的超低延遲?

          首次披露:快手是如何做到百萬觀眾同場看直播仍能秒開且不卡頓的?

          Android直播入門實踐:動手搭建一套簡單的直播系統

          網易云信實時視頻直播在TCP數據傳輸層的一些優化思路

          實時音視頻聊天技術分享:面向不可靠網絡的抗丟包編解碼器

          P2P技術如何將實時視頻直播帶寬降低75%?

          專訪微信視頻技術負責人:微信實時視頻聊天技術的演進

          騰訊音視頻實驗室:使用AI黑科技實現超低碼率的高清實時視頻聊天

          微信團隊分享:微信每日億次實時音視頻聊天背后的技術解密

          近期大熱的實時直播答題系統的實現思路與技術難點分享

          福利貼:最全實時音視頻開發要用到的開源工程匯總

          七牛云技術分享:使用QUIC協議實現實時視頻直播0卡頓!

          實時音視頻聊天中超低延遲架構的思考與技術實踐

          理解實時音視頻聊天中的延時問題一篇就夠

          實時視頻直播客戶端技術盤點:Native、HTML5、WebRTC、微信小程序

          寫給小白的實時音視頻技術入門提綱

          微信多媒體團隊訪談:音視頻開發的學習、微信的音視頻技術和挑戰等

          騰訊技術分享:微信小程序音視頻技術背后的故事

          微信多媒體團隊梁俊斌訪談:聊一聊我所了解的音視頻技術

          新浪微博技術分享:微博短視頻服務的優化實踐之路

          實時音頻的混音在視頻直播應用中的技術原理和實踐總結

          以網游服務端的網絡接入層設計為例,理解實時通信的技術挑戰

          騰訊技術分享:微信小程序音視頻與WebRTC互通的技術思路和實踐

          新浪微博技術分享:微博實時直播答題的百萬高并發架構實踐

          技術干貨:實時視頻直播首屏耗時400ms內的優化實踐

          >> 更多同類文章 ……

          [2] 開源實時音視頻技術WebRTC的文章:

          開源實時音視頻技術WebRTC的現狀

          簡述開源實時音視頻技術WebRTC的優缺點

          訪談WebRTC標準之父:WebRTC的過去、現在和未來

          良心分享:WebRTC 零基礎開發者教程(中文)[附件下載]

          WebRTC實時音視頻技術的整體架構介紹

          新手入門:到底什么是WebRTC服務器,以及它是如何聯接通話的?

          WebRTC實時音視頻技術基礎:基本架構和協議棧

          淺談開發實時視頻直播平臺的技術要點

          [觀點] WebRTC應該選擇H.264視頻編碼的四大理由

          基于開源WebRTC開發實時音視頻靠譜嗎?第3方SDK有哪些?

          開源實時音視頻技術WebRTC中RTP/RTCP數據傳輸協議的應用

          簡述實時音視頻聊天中端到端加密(E2EE)的工作原理

          實時通信RTC技術棧之:視頻編解碼

          開源實時音視頻技術WebRTC在Windows下的簡明編譯教程

          網頁端實時音視頻技術WebRTC:看起來很美,但離生產應用還有多少坑要填?

          了不起的WebRTC:生態日趨完善,或將實時音視頻技術白菜化

          騰訊技術分享:微信小程序音視頻與WebRTC互通的技術思路和實踐

          >> 更多同類文章 ……

          (本文同步發布于:http://www.52im.net/thread-2087-1-1.html



          作者:Jack Jiang (點擊作者姓名進入Github)
          出處:http://www.52im.net/space-uid-1.html
          交流:歡迎加入即時通訊開發交流群 215891622
          討論:http://www.52im.net/
          Jack Jiang同時是【原創Java Swing外觀工程BeautyEye】【輕量級移動端即時通訊框架MobileIMSDK】的作者,可前往下載交流。
          本博文 歡迎轉載,轉載請注明出處(也可前往 我的52im.net 找到我)。


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          Jack Jiang的 Mail: jb2011@163.com, 聯系QQ: 413980957, 微信: hellojackjiang
          主站蜘蛛池模板: 民丰县| 镇江市| 萨嘎县| 十堰市| 佛学| 杂多县| 武陟县| 华容县| 大足县| 大方县| 罗甸县| 梓潼县| 浠水县| 怀集县| 宜黄县| 乌审旗| 奈曼旗| 天柱县| 上饶市| 阿合奇县| 都安| 康定县| 营口市| 肃南| 新宁县| 新巴尔虎右旗| 罗山县| 天峻县| 孟连| 安阳市| 合作市| 天长市| 英超| 兰溪市| 嘉善县| 泰兴市| 额济纳旗| 金门县| 苏尼特右旗| 桓仁| 杭锦旗|