直播首屏耗時400ms以下的優(yōu)化實踐

本文首發(fā)于公號:何俊林

導(dǎo)讀:直播行業(yè)的競爭越來越激烈尔艇,進過18年這波洗牌后尔许,已經(jīng)度過了蠻荒暴力期,剩下的都是在不斷追求體驗终娃。通過多種方案并行味廊,把首開降到500ms以下,希望能對大家有借鑒尝抖。

背景:基于FFmpeg的ijkplayer毡们,最新版本0.88版本。

拉流協(xié)議基于http-flv昧辽,http-flv更穩(wěn)定些衙熔,國內(nèi)大部分直播公司基本都是使用http-flv了,從我們實際數(shù)據(jù)來看搅荞,http-flv確實稍微更快些红氯。但是考慮到會有rtmp播,這塊也加了些優(yōu)化咕痛。

IP直通車

簡單理解就是痢甘,把域名替換成IP。比如https://www.<wbr>baidu.com/茉贡,你可以直接換成14.215.177.39塞栅,這樣做的目的是,省去了DNS解析的耗時腔丧,尤其在網(wǎng)絡(luò)不好時放椰,訪問域名作烟,域名要去解析,再給你返回砾医。不僅僅有時間解析過長的問題拿撩,還有小運營商DNS劫持的問題。一般就是在啟動應(yīng)用時如蚜,就開始對拉流的域名進行預(yù)解析好压恒,存到本地,然后在真正拉流時错邦,直接用就行探赫。典型的案列,就是很多人使用HTTPDNS,這個github上也有開源兴猩,可以自行去研究下期吓。

需要注意的是早歇,這種方案在使用 HTTPS 時倾芝,是會失敗的。因為 HTTPS 在證書驗證的過程箭跳,會出現(xiàn) domain 不匹配導(dǎo)致 SSL/TLS 握手不成功晨另。

服務(wù)端 GOP 緩存

除了客戶端業(yè)務(wù)側(cè)的優(yōu)化外,我們還可以從流媒體服務(wù)器側(cè)進行優(yōu)化谱姓。我們都知道直播流中的圖像幀分為:I 幀借尿、P 幀、B 幀屉来,其中只有 I 幀是能不依賴其他幀獨立完成解碼的路翻,這就意味著當(dāng)播放器接收到 I 幀它能馬上渲染出來,而接收到 P 幀茄靠、B 幀則需要等待依賴的幀而不能立即完成解碼和渲染茂契,這個期間就是「黑屏」了。

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

這里有一個 IDR 幀的概念需要講一下战秋,所有的 IDR 幀都是 I 幀璧亚,但是并不是所有 I 幀都是 IDR 幀,IDR 幀是 I 幀的子集脂信。I 幀嚴格定義是幀內(nèi)編碼幀癣蟋,由于是一個全幀壓縮編碼幀拐袜,通常用 I 幀表示「關(guān)鍵幀」。IDR 是基于 I 幀的一個擴展梢薪,帶了控制邏輯蹬铺,IDR 圖像都是 I 幀圖像,當(dāng)解碼器解碼到 IDR 圖像時秉撇,會立即將參考幀隊列清空甜攀,將已解碼的數(shù)據(jù)全部輸出或拋棄。重新查找參數(shù)集琐馆,開始一個新的序列规阀。這樣如果前一個序列出現(xiàn)重大錯誤,在這里可以獲得重新同步的機會瘦麸。IDR 圖像之后的圖像永遠不會使用 IDR 之前的圖像的數(shù)據(jù)來解碼谁撼。在 H.264 編碼中,GOP 是封閉式的滋饲,一個 GOP 的第一幀都是 IDR 幀厉碟。

推流端設(shè)置

一般播放器需要拿到一個完整的GOP,才能記性播放屠缭。GOP是在推流端可以設(shè)置箍鼓,比如下面這個圖,是我dump一個流呵曹,看到的GOP情況款咖。GOP大小是50,推流過來的fps設(shè)置是25奄喂,也就是1s內(nèi)會顯示25個Frame铐殃,50個Frame,剛好直播設(shè)置GOP 2S跨新,但是直播一般fps不用設(shè)置這么高富腊,可以隨便dump任何一家直播公司的推流,設(shè)置fps在15-18之間就夠了玻蝌。

播放器相關(guān)耗時

當(dāng)set一個源給播放器后蟹肘,播放器需要open這個流,然后和服務(wù)端建立長連接俯树,然后demux帘腹,codec,最后渲染许饿。我們可以按照播放器的四大塊阳欲,依次優(yōu)化

  • 數(shù)據(jù)請求耗時

  • 解復(fù)用耗時

  • 解碼耗時

  • 渲染出圖耗時

數(shù)據(jù)請求

這里就是網(wǎng)絡(luò)和協(xié)議相關(guān)。無論是http-flv,還是rtmp球化,都主要是基于tcp的秽晚,所以一定會有tcp三次握手,同時打開tcp.c分析筒愚。需要加日志在一些方法中赴蝇,如下tcp_open方法。是已經(jīng)改動過的


/* return non zero if error */

static int tcp_open(URLContext *h, const char *uri, int flags)

{

    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));

            return AVERROR(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){

        struct sockaddr_in6 * sockaddr_v6 = (struct sockaddr_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();

        goto fail;

    }

    /* 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)

            goto fail1;

    } else if (s->listen == 1) {

        // single client

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

                                  s->listen_timeout, h)) < 0)

            goto fail1;

        // 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");

            goto fail1;

        }

        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))

                goto fail1;

            if (ret == AVERROR_EXIT)

                goto fail1;

            else

                goto fail;

        } 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");

                goto fail1;

            } else if (!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");

    return 0;

    // 省略部分代碼

}

改動地方主要是hints.ai_family = AF_INET;,原來是 hints.ai_family = AF_UNSPEC;,原來設(shè)計是一個兼容IPv4和IPv6的配置巢掺,如果修改成AF_INET,那么就不會有AAAA的查詢包了句伶。如果只有IPv4的請求,就可以改成AF_INET陆淀。當(dāng)然有IPv6考余,這里就不要動了。這么看是否有轧苫,可以通過抓包工具看楚堤。

接著分析,我們發(fā)現(xiàn)tcp_read函數(shù)是個阻塞式的含懊,會非常耗時身冬,我們又不能設(shè)置短一點中斷時間,因為短了的話绢要,造成讀取不到數(shù)據(jù)吏恭,就中斷拗小,后續(xù)播放就直接失敗了重罪,這里只能讓它等。不過還是優(yōu)化的點時下面部分


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

{

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

    TCPContext *s = h->priv_data;

    int ret;

    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)

        return AVERROR_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);

    return ret < 0 ? ff_neterrno() : ret;

}

我們可以把上面兩行注釋掉哀九,因為在ff_network_wait_fd_timeout等回來后剿配,數(shù)據(jù)可以放到buf中,下面av_application_did_io_tcp_read就沒必要去執(zhí)行了阅束。原來每次ret>0呼胚,都會執(zhí)行av_application_did_io_tcp_read這個函數(shù)。

解復(fù)用耗時

在日志中發(fā)現(xiàn)息裸,數(shù)據(jù)請求到后蝇更,進行音視頻分離時,首先需要匹配對應(yīng)demuxer呼盆,其中ffmpeg的av_find_input_formatavformat_find_stream_info非常耗時年扩,前者簡單理解就是打開某中請求到數(shù)據(jù),后者就是探測流的一些信息访圃,做一些樣本檢測厨幻,讀取一定長度的碼流數(shù)據(jù),來分析碼流的基本信息,為視頻中各個媒體流的 AVStream 結(jié)構(gòu)體填充好相應(yīng)的數(shù)據(jù)况脆。這個函數(shù)中做了查找合適的解碼器饭宾、打開解碼器、讀取一定的音視頻幀數(shù)據(jù)格了、嘗試解碼音視頻幀等工作看铆,基本上完成了解碼的整個流程。這時一個同步調(diào)用盛末,在不清楚視頻數(shù)據(jù)的格式又要做到較好的兼容性時性湿,這個過程是比較耗時的,從而會影響到播放器首屏秒開满败。這兩個函數(shù)調(diào)用都在ff_ffplay.c的read_thread函數(shù)中:


    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");

    }

    else if (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;

        goto fail;

    }

    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;

        goto fail;

#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);

        int orig_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’算墨,以及樣本大小宵荒。

同時在外部可以通過設(shè)置 probesize 和 analyzeduration 兩個參數(shù)來控制該函數(shù)讀取的數(shù)據(jù)量大小和分析時長為比較小的值來降低 avformat_find_stream_info的耗時,從而優(yōu)化播放器首屏秒開净嘀。但是报咳,需要注意的是這兩個參數(shù)設(shè)置過小時,可能會造成預(yù)讀數(shù)據(jù)不足挖藏,無法解析出碼流信息暑刃,從而導(dǎo)致播放失敗、無音頻或無視頻的情況膜眠。所以岩臣,在服務(wù)端對視頻格式進行標準化轉(zhuǎn)碼,從而確定視頻格式宵膨,進而再去推算 avformat_find_stream_info分析碼流信息所兼容的最小的probesizeanalyzeduration架谎,就能在保證播放成功率的情況下最大限度地區(qū)優(yōu)化首屏秒開。

在 FFmpeg 中的 utils.c 文件中的函數(shù)實現(xiàn)中有一行代碼是 int fps_analyze_framecount = 20;辟躏,這行代碼的大概用處是谷扣,如果外部沒有額外設(shè)置這個值,那么 avformat_find_stream_info 需要獲取至少 20 幀視頻數(shù)據(jù)捎琐,這對于首屏來說耗時就比較長了会涎,一般都要 1s 左右。而且直播還有實時性的需求瑞凑,所以沒必要至少取 20 幀末秃。將這個值初始化為2,看看效果拨黔。


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

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

            int fps_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) {

                    int fps_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) {

                        int avg_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 以內(nèi)绰沥。

最后就是解碼耗時和渲染出圖耗時,這塊優(yōu)化空間很少贺待,大頭都在前面徽曲。

有人開始拋出問題了,你這個起播快是快麸塞,但是后面網(wǎng)絡(luò)不好秃臣,卡頓怎么辦?直播中會引起卡頓哪工,主要是網(wǎng)絡(luò)有抖動的時候奥此,沒有足夠的數(shù)據(jù)來播放,ijkplayer會激發(fā)其緩沖機制雁比,主要是有幾個宏控制

  • DEFAULT_FIRST_HIGH_WATER_MARK_IN_MS:網(wǎng)絡(luò)差時首次去喚醒read_thread函數(shù)去讀取數(shù)據(jù)稚虎。

  • DEFAULT_NEXT_HIGH_WATER_MARK_IN_MS:第二次去喚醒read_thread函數(shù)去讀取數(shù)據(jù)。

  • DEFAULT_LAST_HIGH_WATER_MARK_IN_MS這個宏的意思是最后的機會去喚醒read_thread函數(shù)去讀取數(shù)據(jù)偎捎。

可以設(shè)置DEFAULT_LAST_HIGH_WATER_MARK_IN_MS為1 * 1000蠢终,也即緩沖1秒后開始通知緩沖完成去讀取數(shù)據(jù),默認是5秒茴她,如果過大寻拂,會讓用戶等太久,那么每次讀取的bytes也可以少些丈牢〖蓝ぃ可以設(shè)置DEFAULT_HIGH_WATER_MARK_IN_BYTES小一些,設(shè)置為30 * 1024己沛,默認是256 * 1024慌核。把BUFFERING_CHECK_PER_MILLISECONDS設(shè)置為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;
}

最后優(yōu)化的點泛粹,是設(shè)置一些參數(shù)值遂铡,也能優(yōu)化一部分,實際上很多直播用軟件用低分辨率240晶姊,甚至360,來達到秒開伪货,可以可以作為一個減少耗時點來展開的们衙,因為分辨率越低,數(shù)據(jù)量越少碱呼,首開越快蒙挑。


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);
//重連模式,如果中途服務(wù)器斷開了連接愚臀,讓它重新連接
mediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "reconnect", 1); 

以上完了后忆蚀,就可以看下測試數(shù)據(jù),分辨率在540p以下基本秒開,在4G網(wǎng)絡(luò)下測試:

1馋袜、河北衛(wèi)視直播源男旗,測試10組,平均下來300ms欣鳖。一組數(shù)據(jù)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 http://weblive.hebtv.com/live/hbws_bq/index.m3u8
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。一組數(shù)據(jù)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 http://14.215.100.45/hw.pull.inke.cn/live/1542433669916866_0_ud.flv?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因篇。一組數(shù)據(jù)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 http://flv-live-qn.xingxiu.panda.tv/panda-xingxiu/dc7eb0c2e78c96646591aae3a20b0686.flv
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

以上日志在星球里有分享笔横。星球介紹《說一說我在創(chuàng)建星球這10多天竞滓,在星球里干了啥?》吹缔,希望本篇文章能對你有所幫助商佑。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市厢塘,隨后出現(xiàn)的幾起案子茶没,更是在濱河造成了極大的恐慌,老刑警劉巖晚碾,帶你破解...
    沈念sama閱讀 212,185評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抓半,死亡現(xiàn)場離奇詭異,居然都是意外死亡格嘁,警方通過查閱死者的電腦和手機笛求,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,445評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來糕簿,“玉大人探入,你說我怎么就攤上這事《” “怎么了蜂嗽?”我有些...
    開封第一講書人閱讀 157,684評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長殃恒。 經(jīng)常有香客問我植旧,道長辱揭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,564評論 1 284
  • 正文 為了忘掉前任病附,我火速辦了婚禮问窃,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘胖喳。我一直安慰自己泡躯,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,681評論 6 386
  • 文/花漫 我一把揭開白布丽焊。 她就那樣靜靜地躺著较剃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪技健。 梳的紋絲不亂的頭發(fā)上写穴,一...
    開封第一講書人閱讀 49,874評論 1 290
  • 那天,我揣著相機與錄音雌贱,去河邊找鬼啊送。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播械蹋,決...
    沈念sama閱讀 39,025評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼篷朵!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起婆排,我...
    開封第一講書人閱讀 37,761評論 0 268
  • 序言:老撾萬榮一對情侶失蹤声旺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后段只,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體腮猖,經(jīng)...
    沈念sama閱讀 44,217評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,545評論 2 327
  • 正文 我和宋清朗相戀三年赞枕,在試婚紗的時候發(fā)現(xiàn)自己被綠了澈缺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,694評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡鹦赎,死狀恐怖谍椅,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情古话,我是刑警寧澤,帶...
    沈念sama閱讀 34,351評論 4 332
  • 正文 年R本政府宣布锁施,位于F島的核電站陪踩,受9級特大地震影響杖们,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜肩狂,卻給世界環(huán)境...
    茶點故事閱讀 39,988評論 3 315
  • 文/蒙蒙 一摘完、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧傻谁,春花似錦孝治、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,778評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至态蒂,卻和暖如春杭措,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背钾恢。 一陣腳步聲響...
    開封第一講書人閱讀 32,007評論 1 266
  • 我被黑心中介騙來泰國打工手素, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瘩蚪。 一個月前我還...
    沈念sama閱讀 46,427評論 2 360
  • 正文 我出身青樓泉懦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親疹瘦。 傳聞我的和親對象是個殘疾皇子崩哩,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,580評論 2 349

推薦閱讀更多精彩內(nèi)容