Avplayer详细设计

一.   视频显示库(video)

1.      第三方环境

DirectX9.0


2.      对外接口

2.1. 初始化 (d3d_init_video)

EXPORT_API int d3d_init_video(structvo_context *ctx,int w, int h, intpix_fmt);

2.2. 渲染一帧 (d3d_render_one_frame)

EXPORT_API int d3d_render_one_frame(structvo_context *ctx,AVFrame* data, int pix_fmt, doublepts);

2.3. 重置画面大小 (d3d_re_size)

EXPORT_API void d3d_re_size(structvo_context *ctx,int width, int height);

2.4. (d3d_aspect_ratio)

EXPORT_API void d3d_aspect_ratio(structvo_context *ctx,int srcw, int srch, intenable_aspect);

2.5. 刷新屏幕 (d3d_use_overlay)

EXPORT_API int d3d_use_overlay(structvo_context *ctx);

2.6. 释放资源 (d3d_destory_render)

EXPORT_API void d3d_destory_render(structvo_context *ctx);

3.      使用方法

由zlplayer调用,对应用层透明。

3.1.   视频显示结构体 (caller层)

typedefstruct vo_context
{
      int (*init_video)(structvo_context *vo_ctx, int w, int h, int pix_fmt);
      int(*render_one_frame)(struct vo_context *vo_ctx, AVFrame* data, int pix_fmt,double pts);
      void(*re_size)(struct vo_context *vo_ctx, int width, int height);
      void(*aspect_ratio)(struct vo_context *vo_ctx, int srcw, int srch, intenable_aspect);
      int(*use_overlay)(struct vo_context *vo_ctx);
      void(*destory_video)(struct vo_context *vo_ctx);
void *priv;            //video库中真正的视频处理结构 ß(void*)(d3d = new d3d_render);
       void *user_data;     //窗口句柄
       void *user_ctx;      //user context
       float fps;               //frames per seconds ?
}vo_context;

Caller使用video 库的导出函数,为自己的vo_context结构中的函数指针赋值,然后使用。

3.2.   vo_context::init_video

调用函数: player_impl::video_render_thrd

if(!inited && play->m_vo_ctx)
{
       inited = 1;
ret =play->m_vo_ctx->init_video(play->m_vo_ctx, 
play->m_video_ctx->width, 
play->m_video_ctx->height, 
play->m_video_ctx->pix_fmt);
       if (ret != 0)    inited = -1;
       else        play->m_play_status= playing;
}

通过线程的局部变量inited控制video render的初始化。

当inited == 0时,调用m_vo_ctx->init_video初始化video render;

正确初始化:inited ß 1;play->m_play_status ßplaying;

初始化异常:inited ß-1;

线程的其他部分,通过判断inited值来获取video render的初始化状态。

3.3.   vo_context::render_one_frame

调用函数:player_impl::video_render_thrd

线程循环中,两处需要调用render_one_frame函数。

(1) 渲染一个视频帧

if(inited == 1 && play->m_vo_ctx)
{
       play->m_vo_ctx->render_one_frame(play->m_vo_ctx,
&video_frame, 
play->m_video_ctx->pix_fmt,av_curr_play_time(play));
       if (delay != 0)       Sleep(4);
}

(2) 播放器暂停,渲染黑屏               //实时播放,不需要渲染黑屏

while(play->m_play_status == paused && inited == 1 &&play->m_vo_ctx && !play->m_abort)
{
       play->m_vo_ctx->render_one_frame(play->m_vo_ctx,
&video_frame, 
play->m_video_ctx->pix_fmt,av_curr_play_time(play));
       Sleep(16);
}

3.4.   vo_context::re_size

调用函数:player_impl::win_wnd_proc

CaseWM_SIZE:

m_video->re_size(m_video, LOWORD(lparam),HIWORD(lparam));

 

d3d库中没有实现re_size,但是在读RTP流时,是否需要根据SPS和PPS信息,而实现该函数呢?

根据屏幕大小,而改变视频大小,是否也应该实现此函数呢?

注:Ddraw_render::re_size实现了。

 

3.5.   vo_context::aspect_ratio

无调用

3.6.   vo_context::use_overlay

调用函数:player_impl::win_paint(HWND hwnd,HDC hdc)

if(m_avplay &&
       m_avplay->m_vo_ctx &&
       m_video->priv &&
       m_video->use_overlay(m_video) != -1)
{     
RECT client_rect;
       GetClientRect(hwnd, &client_rect);
       fill_rectange(hwnd, hdc, client_rect,client_rect);
}

3.7.   vo_context::destory_video

调用函数:

libav. Free_video_render(vo_context*ctx)
//释放渲染器后,释放vo_context结构
if(ctx->priv)
       ctx->destory_video(ctx);       
free(ctx);

free_video_render调用函数1

:libav.configure(avplay *play, void *param, int type)
case VIDEO_RENDER:
              {
                     if (play->m_vo_ctx&& play->m_vo_ctx->priv)
                            free_video_render(play->m_vo_ctx);
                     play->m_vo_ctx =(vo_context*)param;
              }
              break;

该函数存在风险在free_video_render中,只是free了,没有对指针置NULL,那么这里就有可能再次释放已经释放了的结构。因此需要改进free_video_render函数

 

free_video_render调用函数2:voidav_stop(avplay *play)

停止播放器之前,释放资源。

 

free_video_render调用函数3:player_impl::open()

用于初始化异常时,释放已经初始化的结构。

 

二.   视频解码显示库(libav)

1.      第三方环境

最新版ffmpeg

2.      对外接口

2.1. EXPORT_API source_context*alloc_media_source(int type, const char *addition,

int addition_len, int64_t size);

2.2. EXPORT_API voidfree_media_source(source_context *ctx);

2.3. EXPORT_API ao_context*alloc_audio_render();

2.4. EXPORT_API voidfree_audio_render(ao_context *ctx);

2.5. 创建渲染器EXPORT_APIvo_context* alloc_video_render(void *user_data);

2.6. 释放渲染器EXPORT_APIvoid free_video_render(vo_context *ctx);

2.7. EXPORT_API demux_context*alloc_demux_context();

2.8. EXPORT_API voidfree_demux_context(demux_context *ctx);

2.9. 创建播放器EXPORT_APIavplay* alloc_avplay_context();

2.10. 释放播放器EXPORT_APIvoid free_avplay_context(avplay *ctx);

2.11. 初始化EXPORT_APIint initialize(avplay *play, source_context *sc);

2.12. EXPORT_API int initialize_avplay(avplay*play, const char *file_name, int source_type,

                                                 demux_context*dc);

2.13. 配置EXPORT_APIvoid configure(avplay *play, void *param, int type);

2.14. 开始播放EXPORT_APIint av_start(avplay *play, double fact, int index);

2.15. 等待结束EXPORT_APIvoid wait_for_completion(avplay *play);

2.16. 停止播放.没调用EXPORT_API void av_stop(avplay *play);

2.17. 暂停EXPORT_APIvoid av_pause(avplay *play);

2.18. 重启EXPORT_APIvoid av_resume(avplay *play);

2.19. 跳转EXPORT_APIvoid av_seek(avplay *play, double fact);

2.20. EXPORT_API int av_volume(avplay *play,double l, double r);

2.21. EXPORT_API int audio_is_inited(avplay*play);

2.22. EXPORT_API void av_mute_set(avplay*play, int s);

2.23. 当前主时间EXPORT_APIdouble av_curr_play_time(avplay *play);

2.24. 播放时长EXPORT_APIdouble av_duration(avplay *play);

2.25. 销毁EXPORT_APIvoid av_destory(avplay *play);

2.26. 打开帧率统计EXPORT_API void enable_calc_frame_rate(avplay *play);

2.27. 打开码率统计EXPORT_API void enable_calc_bit_rate(avplay *play);

2.28. EXPORT_API int current_bit_rate(avplay*play); // play->m_real_bit_rate
2.29. EXPORT_API int current_frame_rate(avplay *play);

2.30. EXPORT_API double buffering(avplay*play);

2.31. EXPORT_API voidset_download_path(avplay *play, const char *save_path);

2.32. EXPORT_API void set_youku_type(avplay*play, int type);

2.33. EXPORT_API void blurring(AVFrame*frame,

                                          intfw, int fh, int dx, int dy, int dcx, int dcy);

2.34. EXPORT_API void alpha_blend(AVFrame*frame, uint8_t *rgba,

                                                 intfw, int fh, int rgba_w, int rgba_h, int x, int y);

2.35. EXPORT_API int logger_to_file(constchar* logfile);

2.36. EXPORT_API int close_logger_file();

2.37. EXPORT_API int logger(const char *fmt,...);

3.      功能及使用方法

3.1. source_context* alloc_media_source

功能:根据输入参数,分配填充source_context结构。

调用函数:BOOL player_impl::open(const char*movie, int media_type, int render_type)

if(media_type == MEDIA_TYPE_FILE)
{
       len = strlen(filename);
       m_source =alloc_media_source(MEDIA_TYPE_FILE, filename, len + 1, file_lentgh);  
       init_file_source(m_source);
}

3.2.  void free_media_source

功能:释放source_context及真正的source结构。

调用函数1:voidconfigure(avplay *play, void *param, int type)

caseMEDIA_SOURCE:

if (play->m_play_status == playing || play->m_play_status== paused)
              return;    //不允许重新配置正在使用的播放器
       if (play->m_source_ctx)
       {
              if (play->m_source_ctx&& play->m_source_ctx->priv)
                     play->m_source_ctx->close(play->m_source_ctx);
              free_media_source(play->m_source_ctx);
              play->m_source_ctx =(source_context*)param;
       }

Configure函数,相当于环境重新配置函数

 

调用函数2:libav.read_pkt_thrd

在读数据包的线程循环退出之后,释放媒体源。

 

调用函数3:player_impl::open

当打开播放器失败时,需要释放媒体源。

 

调用函数4:player_impl::close()

if (m_avplay)
       {
              ::av_destory(m_avplay);
              m_avplay = NULL;       .
              m_source = NULL; 
              m_cur_index = -1;
              ::logger("closeavplay.\n");
              return TRUE;
       }
       else
       {
              if (m_source) // m_avplay已经不存在, 手动释放m_source.
              {
                     free_media_source(m_source);
                     m_source = NULL;
              }
       }

从这里可以看出,player_impl::m_source与player_impl::m_avplay.m_source_ctx是同一片区域,释放了一个,就不用释放另一个

3.5. 创建渲染器vo_context* alloc_video_render(void*user_data)

调用函数:player_impl::open(constchar *movie, int media_type, int render_type)

 

3.6. 释放渲染器voidfree_video_render

调用函数1:libav.void configure(avplay *play, void *param, int type)

调用函数2:libav.void av_stop(avplay *play)

调用函数3:player_impl::open失败时

 

3.9. 创建播放器avplay* alloc_avplay_context()

功能:分配avplay空间

调用函数:player_impl::open(const char*movie, int media_type, int render_type)

 

3.10.             释放播放器 void free_avplay_context(avplay*ctx)

功能: 释放avplay空间

调用函数:player_impl::open失败时。

注:libav.av_stop用来停止avplay,释放其内部结构。而free_avplay_context只是简单free结构指针,适用于在结构初始化不成功时,直接释放。此时,avplay里面的相关结构还没有运行起来。

 

3.11. 初始化intinitialize

功能:ffmpeg相关的初始化,open_decoder, init_queue

调用函数:player_impl::open

 

3.13. 配置voidconfigure

功能:利用函数参数重新初始化播放器环境。

调用函数:player_impl::open

初始化播放器、渲染器之后,configure。

 

3.14. 开始播放intav_start

功能:起各种线程

调用函数:player_impl::play

 

3.15. 等待结束voidwait_for_completion

功能:

while (play->m_play_status == playing ||play->m_play_status == paused){
              Sleep(100);
}

调用函数:player_impl::wait_for_completion()àmain. void play_thread(void *param)

 

3.16. 停止播放. 无调用av_stop

流程:

通知各个线程退出;

等待线程退出后,释放资源;

更改播放器状态;

Avformat_network_deinit()

调用函数1:libav. Av_destroy

调用函数2:player_impl::stop()

 

3.17. 暂停voidav_pause

实现:play->m_play_status= paused;

调用函数:player_impl::win_wnd_proc

caseWM_RBUTTONDOWN:

if (m_avplay && m_avplay->m_play_status == playing)
                            pause();
                     elseif (m_avplay && m_avplay->m_play_status == paused)
                            resume();

 

3.18. 重启void av_resume

实现:play->m_play_status= playing;

调用函数:同av_pause

 

3.19. 跳转av_seek

实现:set各种seeking相关avplay成员

调用函数1:libav. read_pkt_thrd(void *param)

       读线程开始之前,跳转到avplay.m_start_time

调用函数2:player_impl::win_wnd_proc

caseWM_LBUTTONDOWN:

 

3.23. 当前主时间av_curr_play_time

实现:如果同步到video,则play->m_video_current_pts_drift+ av_gettime() / 1000000.0f;

 

3.24. 播放时长av_duration

实现:(double)play->m_format_ctx->duration/ AV_TIME_BASE;

功能:可帮助实现av_seek

 

3.25. 销毁av_destroy

if(play->m_play_status != stoped && play->m_play_status != inited)
       {
              /*关闭数据源. */
              if(play->m_source_ctx && play->m_source_ctx->priv)
                     play->m_source_ctx->close(play->m_source_ctx);
              av_stop(play);
       }
       free(play);

可以看出,在如下的播放状态中:

typedef enum play_status{
       inited,playing, paused, completed, stoped
} play_status;

Playing、paused、completed都是播放中的状态,都还有许多内部结构,在free avplay之前,需要av_stop(avplay).

 

4. avplay结构成员

4.1. 起始播放时间m_start_time

在av_start()函数中赋值(play->m_start_time = fact;),默认参数为0。

在read_pkt_thrd线程函数中,用以判断是否需要seek。

 

4.2. 播放状态 m_play_status

(1) 在av_start()函数中,create各个线程之后,play->m_play_status= playing

Av_start()函数在主流程中调用。

(2) 在av_stop()函数中,停止线程、释放播放器内部资源之后,play->m_play_status = stoped

(3) 在av_pause()中,play->m_play_status = paused

(4) 在av_resume()中,play->m_play_status = playing

(5) 在read_pkt_thrd中,当av_read_frame()返回值<0时,play->m_play_status= completed

                                     当av_read_frame()返回值!<0时,play->m_play_status = playing

(6) 在video_render_thrd,在播放器需初始化分支,初始化完毕,play->m_play_status = playing

 

4.3. 终止标识符 m_abort

(1) 在initialize函数中,open decoder之后,初始化为play->m_abort = TRUE;

(2) 在initialize函数中,初始化全局变量flush_pkt/frm后,play->m_abort = FALSE;

(3) 在av_stop函数中,首先play->m_abort = TRUE;

(4) 在read_pkt_thrd中,退出线程循环(读完)后,线程函数返回之前,play->m_abort = TRUE

 

4.4. 缓冲管理

long volatilem_pkt_buffer_size;          //读取数据包占用的缓冲区大小
       pthread_mutex_tm_buf_size_mtx;       //互斥量
       m_buffer;                                                 //当前缓冲大小 占 最大缓冲大小的 百分比%
       #defineMAX_PKT_BUFFER_SIZE   5242880

(1) 在read_pkt_thrd线程函数中,

在跳转分支,清空队列之后,m_pkt_buffer_size = 0;

       当读取数据包达到最大缓冲之后,让系统休眠:

while(play->m_pkt_buffer_size > MAX_PKT_BUFFER_SIZE 
&& !play->m_abort && !play->m_seek_req)

              Sleep(32);

       当读取数据包之后,play->m_pkt_buffer_size+= packet.size; m_buffer = ….

(2) 在video_dec_thrd线程函数中,

当取出一个数据包之后,play->m_pkt_buffer_size -= pkt.size;

 

5. 全局变量flush_pkt / flush_frm

5.1. 在queue_init函数中,

put_queue(q,(void *)&flush_pkt);  put_queue(q,(void*) &flush_frm);

       queue_init在initialize()函数中调用

5.2. 在queue_flush函数中,

if (pkt->pkt.data != flush_pkt.data)
                            av_free_packet(&pkt->pkt);
                     if(pkt->pkt.data[0] != flush_frm.data[0])
                            av_free(pkt->pkt.data[0]);

当flush / clear 队列中的所有元素时,不能释放flush_pkt / flush_frm的元素(指针相同)。

       当queue_end()函数中,需调用queue_flush。在read_pkt_thrd线程函数中,需要seek时,需queue_flush.

5.3. 在initialize函数中

av_init_packet(&flush_pkt);  //初始化flush_pkt空间
flush_pkt.data =“FLUSH”;
flush_frm.data[0]= “FLUSH”;

5.4. 在read_pkt_thrd函数中

       在seek分支,需要queue_flush以刷新当前队列,之后,put_queue(…, &flush_pkt);

 

5.5. 在video_dec_thrd函数中

       在线程函数的循环中,get_queue之后,if(pkt.data == flush_pkt.data) {刷新缓冲区,置m_video_dq中的frame标识为1(跳转),初始化play的一些属性成员。} 即,从现在开始,以前解码的frame都要被seek,不再显示了。

 

注:flush_pkt用来做队列开始元素标识的。每当队列刷新时,首先put flush_pkt /frm元素,以同步队列。如,当从m_video_q中取到flush_pkt时,也就是说flush_pkt后面的元素与以前的元素不连续了,那么m_video_dq中的所有frame需要跳过了

      

另外,flush_frm只是一个标识,是不能被渲染显示的,所以,在video_render_thrd中,当遇到flush_frm时,需要跳过。