直播问题分析总结 -- 跳帧

跳帧策略

跳帧策略是指在播放累积延迟逐渐增大时采取的 " 丢弃非实时数据 " 的策略。

跳帧目的

为了保证实时性。

跳帧的原因分析

直播问题分析总结 -- 跳帧_第1张图片
跳帧的原因汇总
非实时数据来不及处理

由于流媒体服务器本身的处理能力的问题,累计的非实时数据超过一定阈值(比如50帧),此时流媒体服务器可能会采取直接丢弃非实时帧序列的策略,然后直接处理实时帧,此时在客户端看来就会出现跳帧的现象。
除了直播,如果录制的文件存在跳帧的现象(一般只会在大批量录制的时候出现),非常可能是服务器的硬盘读写功能跟不上,来不及写数据就被丢弃了,此时应更换性能更好的硬盘。

CPU占用过高

CPU占用过高,处理能力有限,导致收到的数据被丢弃,继而处理最新的数据,从而出现跳帧现象。

播放器缓冲过小

播放器的缓存时间过小,比如为0或着0.1,也会导致跳帧出现。
增加缓存时间可有效避免跳帧,但会影响实时性。

某次跳帧现象的分析和解决

现象描述

Windows 7下MFC开发的终端软件(下文中简称终端),在内嵌浏览器(Microsoft Web Browser控件)中打开网页。
网页中通过Flash Player播放rtmp流,存在严重的跳帧的现象。
基本上是,正常23秒,然后跳23秒,很是诡异。
处理步骤:

  • Step 1: 用IE直接网页直播
    关闭终端,直接在IE中打开网页查看直播,发现不存在跳帧现象,所以基本断定应该是终端本身处理的问题。
  • Step 2: 排除网络原因。
    分别通过以下ping的方式,
ping 192.168.2.222 -t -l 4096

简单分析了,视频源到流媒体服务器,还有流媒体服务器到终端的网络情况,发现时间基本上都在1~2ms左右,说明网络情况良好。

  • Step 3: 分析流本身的问题。
    获取到rtmp流的实际地址,通过工具将rtmp流直接录制能flv文件,将文件播放后发现,视频连续,也不存在跳帧的现象。说明流数据本身是好的。
  • Step 4: 分析终端的逻辑。
    查看终端的任务浏览器时,发现了一个令人怀疑的现象,CPU的使用率本身并不高,一般在20%~50%左右,但CPU的频率过高,一直在100%以上
    直播问题分析总结 -- 跳帧_第2张图片
    CPU的频率过高

    这时候,怀疑可能是终端程序中,对CPU的处理导致的。
    忽然想起前一阵,在程序中加入了CPU的处理流程,每隔5秒中,获取当前程序占用CPU和系统占用的CPU,返回给服务器。
    代码如下:
#include   
#include 

typedef long long           int64_t;
typedef unsigned long long  uint64_t;

/// 时间转换
static uint64_t file_time_2_utc(const FILETIME* ftime)
{
    LARGE_INTEGER li;

    li.LowPart  = ftime->dwLowDateTime;
    li.HighPart = ftime->dwHighDateTime;

    return li.QuadPart;
}

/// 获得CPU的核数
static int get_processor_number()
{
    SYSTEM_INFO info;
    GetSystemInfo(&info);
    return (int)info.dwNumberOfProcessors;
}

/// 获取指定进程占用的CPU
int get_cpu_usage(int pid)
{  
    /// cpu数量
    static int processor_count_ = -1;

    // 上一次的时间
    static uint64_t last_time_ = 0;
    static uint64_t last_system_time_ = 0;

    FILETIME now;
    FILETIME creation_time;
    FILETIME exit_time;
    FILETIME kernel_time;
    FILETIME user_time;
    uint64_t system_time;
    uint64_t time;
    uint64_t system_time_delta;
    uint64_t time_delta;

    int cpu = -1;

    if(processor_count_ == -1)
    {
        processor_count_ = get_processor_number();
    }

    GetSystemTimeAsFileTime(&now);

    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, pid);
    if (!GetProcessTimes(hProcess, &creation_time, &exit_time, &kernel_time, &user_time))
    {
        return 0;
    }

    system_time = (file_time_2_utc(&kernel_time) + file_time_2_utc(&user_time)) 
                  / processor_count_;  

    time = file_time_2_utc(&now);

    if ((last_system_time_ == 0) || (last_time_ == 0))
    {
        last_system_time_ = system_time;
        last_time_ = time;

        return 0;
    }

    system_time_delta = system_time - last_system_time_;
    time_delta = time - last_time_;

    if (time_delta == 0)
    {
        return 0;
    }

    cpu = (int)((system_time_delta * 100 + time_delta / 2) / time_delta);
    last_system_time_ = system_time;
    last_time_ = time;

    return cpu;
}

/// 获取系统占用的CPU
int get_sys_cpu_usage()  
{  
    /// cpu数量  
    static int processor_count_ = -1;  

    /// 上一次的时间  
    static int64_t last_time_        = 0;  
    static int64_t last_system_time_ = 0;  

    FILETIME now;  
    FILETIME creation_time;  
    FILETIME exit_time;  
    FILETIME kernel_time;  
    FILETIME user_time;  
    int64_t system_time;  
    int64_t time;  
    int64_t system_time_delta;  
    int64_t time_delta;  

    int cpu = -1;  

    if(processor_count_ == -1)  
    {  
        processor_count_ = get_processor_number();  
    }  

    GetSystemTimeAsFileTime(&now);  

    if (!GetProcessTimes(GetCurrentProcess(), &creation_time, &exit_time,  
        &kernel_time, &user_time))  
    {  
        // We don't assert here because in some cases (such as in the Task  Manager)  
        // we may call this function on a process that has just exited but we have  
        // not yet received the notification.  
        return -1;  
    }  

    system_time = (file_time_2_utc(&kernel_time) + file_time_2_utc(&user_time))   
                  / processor_count_;  
    time = file_time_2_utc(&now);  

    if ((last_system_time_ == 0) || (last_time_ == 0))  
    {  
        // First call, just set the last values.  
        last_system_time_ = system_time;  
        last_time_ = time;  

        return -1;  
    }  

    system_time_delta = system_time - last_system_time_;  
    time_delta = time - last_time_;  

    if (time_delta == 0)  
    {
        return -1;  
    }

    // We add time_delta / 2 so the result is rounded.  
    cpu = (int)((system_time_delta * 100 + time_delta / 2) / time_delta);  

    last_system_time_ = system_time;  
    last_time_ = time; 

    return cpu;  
}  

分析发现,禁用CPU操作相关的函数后,跳帧的现象消失。

References:

https://alwaysmavs.gitbooks.io/plplayerkit/content/4%20%E7%9B%B4%E6%92%AD%E7%9B%B8%E5%85%B3%E7%9F%A5%E8%AF%86/4.1%20%E8%B7%B3%E5%B8%A7%E7%AD%96%E7%95%A5.html
http://www.jianshu.com/p/ecf51ee32589

你可能感兴趣的:(直播问题分析总结 -- 跳帧)