zoukankan      html  css  js  c++  java
  • 视频播放器控制原理:ffmpeg之ffplay播放器源代码分析

    版权声明:本文由张坤原创文章,转载请注明出处: 
    文章原文链接:https://www.qcloud.com/community/article/535574001486630869

    来源:腾云阁 https://www.qcloud.com/community

    视频播放器原理其实大抵相同,都是对音视频帧序列的控制。只是一些播放器在音视频同步上可能做了更为复杂的帧预测技术,来保证音频和视频有更好的同步性。

    ffplay是FFMpeg自带的播放器,使用了 ffmpeg解码库和用于视频渲染显示的sdl 库,也是业界播放器最初参考的设计标准。本文对ffplay源码进行分析,试图用更基础而系统的方法,来尝试解开播放器的音视频同步,以及播放/暂停、快进/后退的控制原理。

    由于FFMpeg本身的跨平台特性,相比在移动端看音视频代码,在PC端利用VS查看和调试代码,分析播放器原理,要高效迅速很多。

    由于FFMpeg官方提供的ffmplay在console中进行使用不够直观,本文直接分析CSDN上将ffplay移植到VC的代码(ffplay for MFC)进行分析。

    文章目录:
    一、初探mp4文件
    二、以最简单播放器开始:FFmpeg解码 + SDL显示
    三、先抛五个问题
    四、ffplay代码总体结构
    五、视频播放器的操作控制
    5.1 ffplay所定义的关键结构体VideoState
    5.2 补充基础知识——PTS和DTS
    5.2 如何控制音视频同步
    5.4 如何控制视频的播放和暂停?
    5.5 逐帧播放是如何做的?
    5.6 快进和后退
    六、 这次分析ffplay代码的反省总结

    一、初探mp4文件

    为了让大家对视频文件有一个初步认识,首先来看对一个MP4文件的简单分析,如图1。


    图1 对MP4文件解参

    从图一我们知道,每个视频文件都会有特定的封装格式、比特率、时长等信息。视频解复用之后,就划分为video_stream和audio_stream,分别对应视频流和音频流。

    解复用之后的音视频有自己独立的参数,视频参数包括编码方式、采样率、画面大小等,音频参数包括采样率、编码方式和声道数等。

    对解复用之后的音频和视频Packet进行解码之后,就变成原始的音频(PWM)和视频(YUV/RGB)数据,才可以在进行显示和播放。

    其实这已经差不多涉及到了,视频解码播放的大部分流程,整个视频播放的流程如图2所示。


    图2 视频播放流程(图摘自http://blog.csdn.net/leixiaohua1020/article/details/50534150)

    二、以最简单播放器开始:FFmpeg解码 + SDL显示

    为将问题简单化,先不考虑播放音频,只播放视频,代码流程图如图3所示:


    图3 播放器流程图(图源见水印)

    流程图说明如下:

    1.FFmpeg初始化的代码比较固定,主要目的就是为了设置AVFormatContext实例中相关成员变量的值,调用av_register_all、avformat_open_input av_find_stream_info和avcodec_find_decoder等函数。

      如图4所示,初始化之后的AVFormatContext实例里面具体的值,调用av_find_stream_info就是找到文件中的音视频流数据,对其中的streams(包含音频、视频流)变量进行初始化。
    


    图4 AVFormatContext初始化实例

    2.av_read_frame不断读取stream中的下一帧,对其进行解复用得到视频的AVPacket,随后调用avcodec_decode_video2是视频帧AVPacket进行解码,得到图像帧AVFrame。

    3.得到AVFrame之后,接下来就是放到SDL中进行渲染显示了,也很简单,流程见下面代码注释:

    SDL_Overlay *bmp;
    //将解析得到的AVFrame的数据拷贝到SDL_Overlay实例当中
    SDL_LockYUVOverlay(bmp);
    bmp->pixels[0]=pFrameYUV->data[0];
    bmp->pixels[2]=pFrameYUV->data[1];
    bmp->pixels[1]=pFrameYUV->data[2];    
    bmp->pitches[0]=pFrameYUV->linesize[0];
    bmp->pitches[2]=pFrameYUV->linesize[1];  
    bmp->pitches[1]=pFrameYUV->linesize[2];
    
    SDL_UnlockYUVOverlay(bmp);
    //设置SDL_Rect,因为涉及到起始点和显示大小,用rect进行表示。
    SDL_Rect rect;
    rect.x = 0;   
    rect.y = 0;   
    rect.w = pCodecCtx->width; 
    rect.h = pCodecCtx->height;   
    //将SDL_Overlay数据显示到SDL_Surface当中。
    SDL_DisplayYUVOverlay(bmp, &rect);
    //延时40ms,留足ffmpeg取到下一帧并解码该帧的时间,随后继续读取下一帧
    SDL_Delay(40);
    

    由上面的原理可知,从帧流中获取到AVPacket,并且解码得到AVFrame,渲染到SDL窗口中。


    图5 视频播放状态图

    对视频播放的流程总结一下就是:读取下一帧——>解码——>播放——>不断往复,状态图如图5所示。

    三、先抛五个问题

    本文还是以问题抛问题的思路,以逐步对每个问题进行原理性分析,加深对音视频解码和播放的认识。以下这些问题也是每一个播放器所需要面对的基础问题和原理:

    1.我们在观看电影时发现,电影可以更换不同字幕,甚至不同音频,比如中英文字幕和配音,最后在同一个画面中进行显示,视频关于画面、字幕和声音是如何组合的? 
    其实每一个视频文件,读取出来之后发现,都会被区分不同的流。为了让大家有更具体的理解,以FFMpeg中的代码为例,AVMediaType定义了具体的流类型:

    enum AVMediaType {
    
        AVMEDIA_TYPE_VIDEO,  //视频流
    
        AVMEDIA_TYPE_AUDIO, //音频流
    
        AVMEDIA_TYPE_SUBTITLE, //字幕流
    
    };
    

    利用av_read_frame读取出音视频帧之后,随后就利用avcodec_decode_video2对视频捷星解码,或者调用avcodec_decode_audio4对音频进行解码,得到可以供渲染和显示的音视频原始数据。

    图像和字幕都将会以Surface或者texture的形式,就像Android中的SurfaceFlinger,将画面不同模块的显示进行组合,生成一幅新的图像,显示在视频画面中。

    2.既然视频有帧率的概念,音频有采样率的概念,是否直接利用帧率就可以控制音视频的同步了呢? 
    每一个视频帧和音频帧在时域上都对应于一个时间点,按道理来说只要控制每一个音视频帧的播放时间,就可以实现同步。

    但实际上,对每一帧显示的时间上的精确控制是很难的,更何况音频和视频的解码所需时间不同,极容易引起音视频在时间上的不同步。

    所以,播放器具体是如何做音视频同步的呢?

    3.视频的音频流、视频流和字幕流,他们在时间上是连续的还是离散的?不同流的帧数相同吗? 
    由于计算机只能数字模拟离散的世界,所以在时间上肯定是离散的。那既然是离散的,他们的帧数是否相同呢?

    视频可以理解为诸多音频帧、视频帧和字幕帧在时间上的序列,他们在时间上的时长,跟视频总时长是相同的,但是由于每个帧解码时间不同,必然会导致他们在每帧的时间间隔不相同。

    音频原始数据本身就是采样数据,所以是有固定时钟周期。但是视频假如想跟音频进行同步的话,可能会出现跳帧的情况,每个视频帧播放时间差,都会起伏不定,不是恒定周期。

    所以结论是,三者在视频总时长上播放的帧数肯定是不一样的。

    4.视频播放就是一系列的连续帧不停渲染。对视频的控制操作包括:暂停和播放、快进和后退。那有没有想过,每次快进/后退的幅度,以时间为量度好,还是以每次跳跃的帧数,就是每次快进是前进多长时间,还是前进多少帧。 时间 VS 帧数? 
    由上面问题分析,我们知道,视频是以音频流、视频流和字幕流进行分流的,假如以帧数为基础,由于不同流的帧数量不一定相同,以帧数为单位,很容易导致三个流播放的不一致。

    因此以时间为量度,相对更好,直接搜寻mp4文件流,当前播放时间的前进或后退时长的seek时间点,随后重新对文件流进行分流解析,就可以达到快进和后退之后的音视频同步效果。

    我们可以看到绝大部分播放器,快进/倒退都是以时长为步进的,我们可以看看ffplay是怎么样的,以及是如何实现的。

    5.上一节中,实现的简单播放器,解码和播放都是在同一个线程中,解码速度直接影响播放速度,从而将直接造成播放不流畅的问题。那如何在解码可能出现速度不均匀的情况下,进行流畅的视频播放呢?

    很容易想到,引入缓冲队列,将视频图像渲染显示和视频解码作为两个线程,视频解码线程往队列中写数据,视频渲染线程从队列中读取数据进行显示,这样就可以保证视频是可以流程播放的。

    因此需要采用音频帧、视频帧和字幕帧的三个缓冲队列,那如何保证音视频播放的同步呢?

    PTS是视频帧或者音频帧的显示时间戳,究竟是如何利用起来的,从而控制视频帧、音频帧以及字幕帧的显示时刻呢?

    那我们就可以探寻ffplay,究竟是如何去做缓冲队列控制的。

    所有以上五个问题,我们都将在对ffplay源代码的探寻中,逐步找到更具体的解答。

    四、ffplay代码总体结构


    图6 ffplay代码总体流程

    网上有人做了ffplay的总体流程图,如图6。有了这幅图,代码看起来,就会轻松了很多。流程中具体包含的细节如下:

    1.启动定时器Timer,计时器40ms刷新一次,利用SDL事件机制,触发从图像帧队列中读取数据,进行渲染显示;

    2.stream_componet_open函数中,av_read_frame()读取到AVPacket,随后放入到音频、视频或字幕Packet队列中;

    3.video_thread,从视频packet队列中获取AVPacket并进行解码,得到AVFrame图像帧,放到VideoPicture队列中。

    4..audio_thread线程,同video_thread,对音频Packet进行解码;

    5.subtitle_thread线程,同video_thread,对字幕Packet进行解码。

    五、视频播放器的操作控制

    视频播放器的操作包括播放/暂停、快进/倒退、逐帧播放等,这些操作的实现原理是什么呢,下面对其从代码层面逐个进行分析。

    5.1 ffplay所定义的关键结构体VideoState

    与FFmpeg解码类似,定义了一个AVFormatContext结构体,用于存储文件名、音视频流、解码器等字段,供全局进行访问。

    ffplay也定义了一个结构体VideoState,通过对VideoState的分析,就可以大体知道播放器基本实现原理。

    typedef struct VideoState {
           // Demux解复用线程,读视频文件stream线程,得到AVPacket,并对packet入栈
           SDL_Thread *read_tid;  
           //视频解码线程,读取AVPacket,decode 爬出可以成AVFrame并入队
           SDL_Thread *video_tid;
           //视频播放刷新线程,定时播放下一帧
           SDL_Thread *refresh_tid;
           int paused;  //控制视频暂停或播放标志位
           int seek_req;  //进度控制标志
           int seek_flags;
    
           AVStream *audio_st;   //音频流
           PacketQueue audioq;  //音频packet队列
           double audio_current_pts;  //当前音频帧显示时间
    
           AVStream *subtitle_st; //字幕流
           PacketQueue subtitleq;//字幕packet队列 
    
           AVStream *video_st; //视频流
    
           PacketQueue videoq;//视频packet队列
           double video_current_pts; ///当前视频帧pts
           double video_current_pts_drift;  
    
           VideoPicture pictq[VIDEO_PICTURE_QUEUE_SIZE];  //解码后的图像帧队列
    }
    

    从VideoState结构体中可以看出:

    1.解复用、视频解码和视频刷新播放,分属三个线程中,并行控制;

    2.音频流、视频流、字幕流,都有自己的缓冲队列,供不同线程读写,并且有自己的当前帧的PTS;

    3.解码后的图像帧单独放在pictq队列当中,SDL利用其进行显示。

    其中PTS是什么呢,这在音视频中是一个很重要的概念,直接决定视频帧或音频帧的显示时间,下面具体介绍一下。

    5.2 补充基础知识——PTS和DTS


    图7 音视频解码分析

    图7为输出的音频帧和视频帧序列,每一帧都有PTS和DTS标签,这两个标签究竟是什么意思呢?
    DTS(Decode Time Stamp)和PTS(Presentation Time Stamp)都是时间戳,前者是解码时间,后者是显示时间,都是为视频帧、音频帧打上的时间标签,以更有效地支持上层应用的同步机制。

    也就是说,视频帧或者音频在解码时,会记录其解码时间,视频帧的播放时间依赖于PTS。

    对于声音来说 ,这两个时间标签是相同的;但对于某些视频编码格式,由于采用了双向预测技术,DTS会设置一定的超时或延时,保证音视频的同步,会造成DTS和PTS的不一致。

    5.3 如何控制音视频同步

    我们已经知道,视频帧的播放时间其实依赖pts字段的,音频和视频都有自己单独的pts。但pts究竟是如何生成的呢,假如音视频不同步时,pts是否需要动态调整,以保证音视频的同步?

    下面先来分析,如何控制视频帧的显示时间的:

    static void video_refresh(void *opaque){ 
    
      //根据索引获取当前需要显示的VideoPicture
      VideoPicture *vp = &is->pictq[is->pictq_rindex];
    
      if (is->paused)
          goto display; //只有在paused的情况下,才播放图像
    
      // 将当前帧的pts减去上一帧的pts,得到中间时间差
    
      last_duration = vp->pts - is->frame_last_pts;
    
      //检查差值是否在合理范围内,因为两个连续帧pts的时间差,不应该太大或太小
    
      if (last_duration > 0 && last_duration < 10.0) {
        /* if duration of the last frame was sane, update last_duration in video state */
        is->frame_last_duration = last_duration;
      }
    
      //既然要音视频同步,肯定要以视频或音频为参考标准,然后控制延时来保证音视频的同步,
      //这个函数就做这个事情了,下面会有分析,具体是如何做到的。
      delay = compute_target_delay(is->frame_last_duration, is);
    
      //获取当前时间
      time= av_gettime()/1000000.0;
    
       //假如当前时间小于frame_timer + delay,也就是这帧改显示的时间超前,还没到,就直接返回
      if (time < is->frame_timer + delay) 
          return;
    
      //根据音频时钟,只要需要延时,即delay大于0,就需要更新累加到frame_timer当中。
      if (delay > 0)
           /更新frame_timer,frame_time是delay的累加值
           is->frame_timer += delay * FFMAX(1, floor((time-is->frame_timer) / delay));
    
      SDL_LockMutex(is->pictq_mutex);
    
      //更新is当中当前帧的pts,比如video_current_pts、video_current_pos 等变量
      update_video_pts(is, vp->pts, vp->pos);
    
      SDL_UnlockMutex(is->pictq_mutex);
    
    display:
      /* display picture */
      if (!display_disable)
        video_display(is);
    }
    

    函数compute_target_delay根据音频的时钟信号,重新计算了延时,从而达到了根据音频来调整视频的显示时间,从而实现音视频同步的效果。

    static double compute_target_delay(double delay, VideoState *is)
    {
        double sync_threshold, diff;
       //因为音频是采样数据,有固定的采用周期并且依赖于主系统时钟,要调整音频的延时播放较难控制。所以实际场合中视频同步音频相比音频同步视频实现起来更容易。
       if (((is->av_sync_type == AV_SYNC_AUDIO_MASTER && is->audio_st) ||
         is->av_sync_type == AV_SYNC_EXTERNAL_CLOCK)) {
    
           //获取当前视频帧播放的时间,与系统主时钟时间相减得到差值
           diff = get_video_clock(is) - get_master_clock(is);
           sync_threshold = FFMAX(AV_SYNC_THRESHOLD, delay);
    
          //假如当前帧的播放时间,也就是pts,滞后于主时钟
          if (fabs(diff) < AV_NOSYNC_THRESHOLD) {
             if (diff <= -sync_threshold)
                 delay = 0;
          //假如当前帧的播放时间,也就是pts,超前于主时钟,那就需要加大延时
          else if (diff >= sync_threshold)
            delay = 2 * delay;
          }
    
       }
       return delay;
    }
    


    图8 音视频帧显示序列

    所以这里的流程就很简单了,图8简单画了一个音视频帧序列,想表达的意思是,音频帧数量和视频帧数量不一定对等,另外每个音频帧的显示时间在时间上几乎对等,每个视频帧的显示时间,会根据具体情况有延时显示,这个延时就是有上面的compute_target_delay函数计算出来的。

    计算延迟后,更新pts的代码如下:

    static void update_video_pts(VideoState *is, double pts, int64_t pos) {
    
        double time = av_gettime() / 1000000.0;
        /* update current video pts */
        is->video_current_pts = pts;
        is->video_current_pts_drift = is->video_current_pts - time;
        is->video_current_pos = pos;
        is->frame_last_pts = pts;
    }
    

    整个流程可以概括为:
    显示第一帧视频图像;
    根据音频信号,计算出第二帧的delay时间,更新该帧的pts。
    当pts到达后,显示第二帧视频图像。
    重复以上步骤,到最后一帧
    也许在这里仍然会让人很困惑,为什么单单根据主时钟,就可以播放下一帧所需要的延时呢? 
    其实视频是具备一定长度的播放流,具体可以分为音频流、视频流和字幕流,三者同时在一起播放形成了视频,当然他们总的播放时间是跟视频文件的播放时长是一样的。

    由于音频流本身是pwm采样数据,以固定的频率播放,这个频率是跟主时钟相同或是它的分频,从时间的角度来看,每个音频帧是自然均匀流逝。

    所以音频的话,直接按照主时钟或其分频走就可以了。

    视频,要根据自己的显示时间即pts,跟主时钟当前的时间进行对比,确定是超前还是滞后于系统时钟,从而确定延时,随后进行准确的播放,这样就可以保证音视频的同步了。

    那接下来,还有一个问题,计算出延时之后,难道需要sleep一下做延迟显示吗? 
    其实并不是如此,上面分析我们知道delay会更新到当前需要更新视频帧的pts (video_current_pts),对当前AVFrame进行显示前,先检测其pts时间,假如还没到,就不进行显示了,直接return。直到下一次刷新,重新进行检测(ffplay采用的40ms定时刷新)。

    代码如下,未到更新后的pts时间( is->frame_timer + dela),直接return:

    if (av_gettime()/1000000.0 < is->frame_timer + delay)  
        return;
    

    那接下来就是分析如何播放视频帧,就很简单了,只是这里多加了一个字幕流的处理:

    static void video_image_display(VideoState *is)
    {
        VideoPicture *vp;
       SubPicture *sp;
       AVPicture pict;
       SDL_Rect rect;
       int i;
       vp = &is->pictq[is->pictq_rindex];
       if (vp->bmp) {
           //字幕处理
           if (is->subtitle_st) {}                  
       }
    
       //计算图像的显示区域
       calculate_display_rect(&rect, is->xleft, is->ytop, is->width, is->height, vp);
    
       //显示图像
       SDL_DisplayYUVOverlay(vp->bmp, &rect);
    
       //将pic队列的指针向前移动一个位置
       pictq_next_picture(is);
    
    }
    

    VIDEO_PICTURE_QUEUE_SIZE 只设置为4,很快就会用完了。数据满了如何重新更新呢? 
    一旦检测到超出队列大小限制,就处于等待状态,直到pictq被取出消费,从而避免开启播放器,就把整个文件全部解码完,这样会代码会很吃内存。

    static int queue_picture(VideoState *is, AVFrame *src_frame, double pts1, int64_t pos){
    
    /* keep the last already displayed picture in the queue */
    while (is->pictq_size >= VIDEO_PICTURE_QUEUE_SIZE - 2 &&
          !is->videoq.abort_request) {
    
        SDL_CondWait(is->pictq_cond, is->pictq_mutex);
       }
       SDL_UnlockMutex(is->pictq_mutex);
    }
    

    5.4 如何控制视频的播放和暂停?

    static void stream_toggle_pause(VideoState *is)
    {
    
        if (is->paused) {
           //由于frame_timer记下来视频从开始播放到当前帧播放的时间,所以暂停后,必须要将暂停的时间( is->video_current_pts_drift - is->video_current_pts)一起累加起来,并加上drift时间。
    
         is->frame_timer += av_gettime() / 1000000.0 + is->video_current_pts_drift - is->video_current_pts;
    
         if (is->read_pause_return != AVERROR(ENOSYS)) {
         //并更新video_current_pts
            is->video_current_pts = is->video_current_pts_drift + av_gettime() / 1000000.0;
    
           }
        //drift其实就是当前帧的pts和当前时间的时间差
        is->video_current_pts_drift = is->video_current_pts - av_gettime() / 1000000.0;
        }
    
        //paused取反,paused标志位也会控制到图像帧的展示,按一次空格键实现暂停,再按一次就实现播放了。
        is->paused = !is->paused;
    }
    

    特别说明:paused标志位控制着视频是否播放,当需要继续播放的时候,一定要重新更新当前所需要播放帧的pts时间,因为这里面要加上已经暂停的时间。

    5.5 逐帧播放是如何做的?

    在视频解码线程中,不断通过stream_toggle_paused,控制对视频的暂停和显示,从而实现逐帧播放:

    static void step_to_next_frame(VideoState *is)
    {
       //逐帧播放时,一定要先继续播放,然后再设置step变量,控制逐帧播放
       if (is->paused)
          stream_toggle_pause(is);//会不断将paused进行取反
       is->step = 1;
    }
    

    其原理就是不断的播放,然后暂停,从而实现逐帧播放:

    static int video_thread(void *arg)
    {
      if (is->step)
        stream_toggle_pause(is);
          ……………………
      if (is->paused)
        goto display;//显示视频
      }
    }
    

    5.6 快进和后退

    关于快进/后退,首先抛出两个问题:

    1. 快进以时间为维度还是以帧数为维度来对播放进度进行控制呢? 
    2.一旦进度发生了变化,那么当前帧,以及AVFrame队列是否需要清零,整个对stream的流是否需要重新来进行控制呢? 
    ffplay中采用以时间为维度的控制方法。对于快进和后退的控制,都是通过设置VideoState的seek_req、seek_pos等变量进行控制

    do_seek:
    //实际上是计算is->audio_current_pts_drift + av_gettime() / 1000000.0,确定当前需要播放帧的时间值
    pos = get_master_clock(cur_stream);
    pos += incr; //incr为每次快进的步进值,相加即可得到快进后的时间点
    stream_seek(cur_stream, (int64_t)(pos AV_TIME_BASE), (int64_t)(incr AV_TIME_BASE), 0);
    关于stream_seek的代码如下,其实就是设置VideoState的相关变量,以控制read_tread中的快进或后退的流程:

    /* seek in the stream */
    static void stream_seek(VideoState *is, int64_t pos, int64_t rel, int seek_by_bytes)
    {
    
      if (!is->seek_req) {
      is->seek_pos = pos;
      is->seek_rel = rel;
      is->seek_flags &= ~AVSEEK_FLAG_BYTE;
      if (seek_by_bytes)
        is->seek_flags |= AVSEEK_FLAG_BYTE;
      is->seek_req = 1;
    }
    }
    

    stream_seek中设置了seek_req标志,就直接进入前进/后退控制流程了,其原理是调用avformat_seek_file函数,根据时间戳控制索引点,从而控制需要显示的下一帧:

    static int read_thread(void *arg){
    //当调整播放进度以后
    if (is->seek_req) {
       int64_t seek_target = is->seek_pos;
       int64_t seek_min    = is->seek_rel > 0 ? seek_target - is->seek_rel + 2: INT64_MIN;
       int64_t seek_max    = is->seek_rel < 0 ? seek_target - is->seek_rel - 2: INT64_MAX;
      //根据时间抽查找索引点位置,定位到索引点之后,下一帧的读取直接从这里开始,就实现了快进/后退操作
      ret = avformat_seek_file(is->ic, -1, seek_min, seek_target, seek_max, is->seek_flags);
      if (ret < 0) {
         fprintf(stderr, "s: error while seeking
    ", is->ic->filename);
      } else {
      //查找成功之后,就需要清空当前的PAcket队列,包括音频、视频和字幕
         if (is->audio_stream >= 0) {
            packet_queue_flush(&is->audioq);
            packet_queue_put(&is->audioq, &flush_pkt);
         }
         if (is->subtitle_stream >= 0) {//处理字幕stream
            packet_queue_flush(&is->subtitleq);
            packet_queue_put(&is->subtitleq, &flush_pkt);
        }
        if (is->video_stream >= 0) {
           packet_queue_flush(&is->videoq);
           packet_queue_put(&is->videoq, &flush_pkt);
        }
      }
      is->seek_req = 0;
      eof = 0;
      }
    }
    

    另外从上面代码中发现,每次快进后退之后都会对audioq、videoq和subtitleq进行flush清零,也是相当于重新开始,保证缓冲队列中的数据的正确性。

    对于音频,开始仍然有些困惑,因为在暂停的时候,没有看到对音频的控制,是如何控制的呢?

    后来发现,其实暂停的时候设置了is->paused变量,解复用和音频解码和播放都依赖于is->paused变量,所以音频和视频播放都随之停止了。

    六、 这次分析ffplay代码的反省总结:

    1.基础概念和原理积累,最开始接触FFmpeg,因为其涉及的概念很多,看起来有种无从下手的感觉。这时候必须从基本模块入手,逐步理解更多,一定的量积累,就会产生一些质变,更好的理解视频编解码机制;

    2.一定要首先看懂代码总体架构和流程,随后针对每个细节点进行深入分析,会极大提高看代码效率。会画一些框图是非常重要的,比如下面这张,所以简要的流程图要比注重细节的uml图要方便得多;


    3.看FFmpeg代码,在PC端上调试,会快捷很多。假如要在Android上,调用jni来看代码,效率就会很低。

    参考文章:

    基于ffmpeg的跨平台播放器实现

    https://www.qcloud.com/community/article/309889001486708756

    雷神的文章(多媒体入门开发必看):

    http://blog.csdn.net/leixiaohua1020/article/details/15811977

  • 相关阅读:
    OSError: [Errno 13] Permission denied: '/Library/Python/2.7/site-packages/django'
    mac 安装pip
    同学公司倒闭了
    web开发中的字体选择(同事分享)
    svg 学习笔记
    用highchaarts做股票分时图
    highcharts,highStock 中文图表配置
    为什么使用 npm Scripts 构建项目
    JS 浮点型计算的精度问题 推荐的js 库 推荐的类库 Numeral.js 和 accounting.js
    HTML代码转换为JavaScript字符串
  • 原文地址:https://www.cnblogs.com/boonya/p/8487811.html
Copyright © 2011-2022 走看看