avplayer学习笔记

Avplayer详细设计

一.   视频显示库(video)

1.      第三方环境

DirectX9.0

 

2.      对外接口

2.1. 初始化 (d3d_init_video)

EXPORT_API int d3d_init_video(struct vo_context *ctx, int w, int h, int pix_fmt);

2.2. 渲染一帧 (d3d_render_one_frame)

EXPORT_API int d3d_render_one_frame(struct vo_context *ctx, AVFrame* data, int pix_fmt, double pts);

2.3. 重置画面大小 (d3d_re_size)

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

2.4. (d3d_aspect_ratio)

EXPORT_API void d3d_aspect_ratio(struct vo_context *ctx, int srcw, int srch, int enable_aspect);

2.5. 刷新屏幕 (d3d_use_overlay)

EXPORT_API int d3d_use_overlay(struct vo_context *ctx);

2.6. 释放资源 (d3d_destory_render)

EXPORT_API void d3d_destory_render(struct vo_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时,需要跳过。

标签