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时,需要跳过。