1.前言

  出于学习音视频的目的,在Github找了个基于FFMPEG的播放器代码,代码量比较小。地址:fflayer。于是乎下载编译了下,运行结果良好。So,出于学习的目的,写写学习笔记,归纳归纳。该开源代码使用的ffmpeg函数有些被标记成过时的换成最新的会出现闪屏以及看直播时视频声音不同步等各种问题。在后续解析完再慢慢琢磨怎么解决这些问题以及自己可以尝试写个简易播放器加深理解。

2.源码分析

  鉴于个人的习惯,从点到面学习一个工程,首先分析下该工程中参数设置。工程中的主入口ffplayer.cpp,那么我们也从这个文件入手分。参数相关的有三个函数:

//在创建播放器的时候会一同传入,用于配置播放器的各个参数。
void player_load_params(PLAYER_INIT_PARAMS *params, char *str)
//设置参数
void player_setparam(void *hplayer, int id, void *param)
//获取参数
void player_getparam(void *hplayer, int id, void *param)

2.1加载参数

先看看代码:

void player_load_params(PLAYER_INIT_PARAMS *params, char *str)
{params->video_stream_cur    = parse_params(str, "video_stream_cur"   );params->video_thread_count  = parse_params(str, "video_thread_count" );params->video_hwaccel       = parse_params(str, "video_hwaccel"      );params->video_deinterlace   = parse_params(str, "video_deinterlace"  );params->video_rotate        = parse_params(str, "video_rotate"       );params->audio_stream_cur    = parse_params(str, "audio_stream_cur"   );params->subtitle_stream_cur = parse_params(str, "subtitle_stream_cur");params->vdev_render_type    = parse_params(str, "vdev_render_type"   );params->adev_render_type    = parse_params(str, "adev_render_type"   );params->init_timeout        = parse_params(str, "init_timeout"       );params->open_syncmode       = parse_params(str, "open_syncmode"      );
}

  参数str是传入的配置字符串,经过函数parse_params的解析将参数值赋值给params。那么接下来看看parse_params函数:

//标注1 "video_hwaccel=1;video_rotate=0"
static int parse_params(const char *str, const char *key)
{char  t[12];char *p = (char*)strstr(str, key);int   i;//标注2 得到"=1;video_rotate=0"if (!p) return 0;p += strlen(key);if (*p == '\0') return 0;while (1) {if (*p != ' ' && *p != '=' && *p != ':') break;else p++;}//标注3 "1;video_rotate=0"for (i=0; i<12; i++) {if (*p == ',' || *p == ';' || *p == '\n' || *p == '\0') {t[i] = '\0';break;} else {t[i] = *p++;}}t[11] = '\0';//标注4 "1\0"//把字符串转换成整型数return atoi(t);
}

  首先demo里传入的值是"video_hwaccel=1;video_rotate=0"
  先返回player_load_params看下,调了11次parse_params。除了parse_params(str, "video_hwaccel" )parse_params(str, "video_rotate" ),其他在标注2处直接返回。
  先拿parse_params(str, "video_hwaccel" )出来分析。在标注2处经过strstr函数过滤出"=1;video_rotate=0"

在标注3处过滤掉空格、冒号、等号,得到"1;video_rotate=0"。在标注4出将之前得到的第一个有效数字过滤出来存在t字符中并在最后加上’\0’结束符并利用atoi转成整数后返回。

  parse_params(str, "video_rotate" )也是同样的道理。于是player_load_paramsparse_params(str, "video_hwaccel" )parse_params(str, "video_rotate" )分别得到了1和0并赋值给params对应的变量。而其他的得到的还是空。

2.1设置参数

先看下该函数代码:

void player_setparam(void *hplayer, int id, void *param) {if (!hplayer) return;PLAYER *player = (PLAYER *) hplayer;switch (id) {case PARAM_VIDEO_MODE:player->vdmode = *(int *) param;player_setrect(hplayer, 0,player->vdrect.left, player->vdrect.top,player->vdrect.right - player->vdrect.left,player->vdrect.bottom - player->vdrect.top);break;default:render_setparam(player->render, id, param);break;}
}

2.1.1设置屏幕尺寸

当id是PARAM_VIDEO_MODE 的时候,主要还是调用player_setrect来实现。看下player_setrect代码:

void player_setrect(void *hplayer, int type, int x, int y, int w, int h) {if (!hplayer) return;PLAYER *player = (PLAYER *) hplayer;***int vw = player->init_params.video_owidth;int vh = player->init_params.video_oheight;int rw = 0, rh = 0;if (!vw || !vh) return;***//标注1switch (player->vdmode) {case VIDEO_MODE_LETTERBOX:if (w * vh < h * vw) {rw = w;rh = rw * vh / vw;}else {rh = h;rw = rh * vw / vh;}break;case VIDEO_MODE_STRETCHED:rw = w;rh = h;break;}if (rw <= 0) rw = 1;if (rh <= 0) rh = 1;render_setrect(player->render, type, x + (w - rw) / 2, y + (h - rh) / 2, rw, rh);
}

先明确下这几个变量的定义:

w,h表示想要设置的宽和长

rw,rh表示实际要渲染的宽和长

vw,vh表示想要解码出视频的宽和长

再看下代码,其他部分都是简单的赋值。重点看下标注1处按我理解应该VIDEO_MODE_LETTERBOX表示按比例伸缩而VIDEO_MODE_STRETCHED是按原尺寸输出。

  • VIDEO_MODE_STRETCHED比较简单rw = wrh = h,则接下来就是render_setrect(player->render, type, x, y , w, h)。也就是说按照设置的尺寸显示在屏幕上。

  • VIDEO_MODE_LETTERBOX的话有一点逻辑,比如说if(w * vh < h * vw)的情况,换一种写法更清晰。if(w/h<vw/vh),也就是想要设置的宽长比小于视频的宽长比。那么将要设置的w赋值给rw,而高度根据解码出视频的宽比进行压缩。然后接下来的render_setrect函数中,x + (w - rw) / 2也就是将渲染的区域居中在想要设置的区域居中。整体流程如下图所示:

2.1.1设置其他参数

这里主要介绍一下设置速度和声音,其他貌似也没用到。先不管,函数如下:

void render_setparam(void *hrender, int id, void *param)
{if (!hrender) return;RENDER *render = (RENDER*)hrender;switch (id){case PARAM_AUDIO_VOLUME:adev_setparam(render->adev, id, param);break;case PARAM_PLAY_SPEED:render_setspeed(render, *(int*)param);break;***}
}

设置声音:adev_setparam,这个简单。只是往变量里填充声音数值。

设置速度:代码如下:

// 内部函数实现
static void render_setspeed(RENDER *render, int speed)
{if (speed > 0) {// set vdev frame rateint framerate = (int)((render->frame_rate.num * speed) / (render->frame_rate.den * 100.0) + 0.5);vdev_setparam(render->vdev, PARAM_VDEV_FRAME_RATE, &framerate);// set render_speed_new to triger swr_context re-createrender->render_speed_new = speed;}
}void vdev_setparam(void *ctxt, int id, void *param)
{if (!ctxt || !param) return;VDEV_COMMON_CTXT *c = (VDEV_COMMON_CTXT*)ctxt;switch (id) {case PARAM_VDEV_FRAME_RATE:c->tickframe = 1000 / (*(int*)param > 1 ? *(int*)param : 1);break;***}if (c->setparam) c->setparam(c, id, param);
}

默认的情况下speed是100,也就是说默认情况下int framerate = (int)(render->frame_rate.num / (render->frame_rate.den + 0.5)。至于为什么是这两个东西,跟同步有关系放在分析同步的时候再说。接下来调用vdev_setparam函数。将得到的乘以1000也就是得到毫秒数赋值给c->tickframe,以供后续同步的时候用。

2.2获取参数

先看看代码:

void player_getparam(void *hplayer, int id, void *param) {if (!hplayer || !param) return;PLAYER *player = (PLAYER *) hplayer;switch (id) {case PARAM_MEDIA_DURATION:if (!player->avformat_context) *(int64_t *) param = 1;else *(int64_t *) param = (player->avformat_context->duration * 1000 / AV_TIME_BASE);if (*(int64_t *) param <= 0) *(int64_t *) param = 1;break;case PARAM_MEDIA_POSITION:if ((player->player_status & PS_F_SEEK) ||(player->player_status & player->seek_req) == player->seek_req) {*(int64_t *) param = player->seek_dest - player->start_pts;} else {int64_t pos = 0;render_getparam(player->render, id, &pos);switch (pos) {case -1:*(int64_t *) param = -1;break;case AV_NOPTS_VALUE:*(int64_t *) param = player->seek_dest - player->start_pts;break;default:*(int64_t *) param = pos - player->start_pts;break;}}break;case PARAM_VIDEO_WIDTH:if (!player->vcodec_context) *(int *) param = 0;else *(int *) param = player->init_params.video_owidth;break;case PARAM_VIDEO_HEIGHT:if (!player->vcodec_context) *(int *) param = 0;else *(int *) param = player->init_params.video_oheight;break;case PARAM_VIDEO_MODE:*(int *) param = player->vdmode;break;case PARAM_RENDER_GET_CONTEXT:*(void **) param = player->render;break;default:render_getparam(player->render, id, param);break;}
}void render_getparam(void *hrender, int id, void *param)
{if (!hrender) return;RENDER         *render = (RENDER*)hrender;VDEV_COMMON_CTXT *vdev = (VDEV_COMMON_CTXT*)render->vdev;switch (id){case PARAM_MEDIA_POSITION:if (vdev->status & VDEV_COMPLETED) {*(int64_t*)param  = -1; // means completed} else {*(int64_t*)param = vdev->apts > vdev->vpts ? vdev->apts : vdev->vpts;}break;***}
}
  • 获取播放时间总长度:当PARAM_MEDIA_DURATION的时候,player->avformat_context->duration / AV_TIME_BASE表示总时间是秒,乘以1000将返回的值转为毫秒。
  • 获取长、宽以及播放模式:从player获取参数并直接返回。
  • 获取当前播放位置:当正在拖动的时候或者正在请求拖动的时候,返回seek_dest与起始时间戳的差值。当正常播放的时候,先调用render_getparam函数。播放完成那么返回-1,还没完成就返回音频或视频时间戳里比较快的那个。回到player_getparam方法,pos如果返回-1,则给param赋值-1.如果AV_NOPTS_VALUE,则返回seek_dest与起始时间戳的差值。其他情况表示正常,返回时间戳posstart_pts的差值。
  • 获取其他的参数直接调用render_getparam函数,都是从render中获取参数。
void render_getparam(void *hrender, int id, void *param)
{if (!hrender) return;RENDER         *render = (RENDER*)hrender;VDEV_COMMON_CTXT *vdev = (VDEV_COMMON_CTXT*)render->vdev;switch (id){***case PARAM_AUDIO_VOLUME:adev_getparam(render->adev, id, param);break;case PARAM_PLAY_SPEED:*(int*)param = render->render_speed_cur;break;case PARAM_AVSYNC_TIME_DIFF:case PARAM_VDEV_GET_D3DDEV:vdev_getparam(render->vdev, id, param);break;case PARAM_ADEV_GET_CONTEXT:*(void**)param = render->adev;break;case PARAM_VDEV_GET_CONTEXT:*(void**)param = render->vdev;break;}
}

学习开源播放器代码解析之参数设置相关推荐

  1. 搭建rtmp直播流服务之4:videojs和ckPlayer开源播放器二次开发(播放rtmp、hls直播流及普通视频)...

    前面几章讲解了使用 nginx-rtmp搭建直播流媒体服务器; ffmpeg推流到nginx-rtmp服务器; java通过命令行调用ffmpeg实现推流服务; 从数据源获取,到使用ffmpeg推流, ...

  2. 开源播放器 ijkplayer (一) :使用Ijkplayer播放直播视频

    1.ijkplayer 编码 IjkPlayer支持硬解码和软解码. 软解码时不会旋转视频角度这时需要你通过onInfo的what == IMediaPlayer.MEDIA_INFO_VIDEO_R ...

  3. 网吧做直播,制做网页播放器代码全集

    <script src="/script/ShowHidden.js" type="text/javascript"></script>

  4. java用户注册代码解析_java springmvc 注册中央调度器代码解析

    java springmvc 注册中央调度器代码解析 发布时间:2020-08-22 20:58:23 来源:脚本之家 阅读:107 作者:荆棘人 这篇文章主要介绍了java springmvc 注册 ...

  5. JS单曲调用百度mp3音乐播放器代码

    在网上找的单曲调用百度mp3音乐播放器的代码,改了改,文本框输入歌曲名,中文逗号,歌手名,回车就可以试听了.几点说明:  百度音乐api 音乐文件地址,span,p,div,等都可以,ID 必须是so ...

  6. 基于python的音频播放器_基于python实现音乐播放器代码实例

    基于python实现音乐播放器代码实例,一首,函数,按钮,布局,音乐 基于python实现音乐播放器代码实例 易采站长站,站长之家为您整理了基于python实现音乐播放器代码实例的相关内容. 核心播放 ...

  7. 经常使用网页播放器代码

    经常使用网页播放器代码 我们在网页上看到的播放器无外乎WMP/RealOne/Macromedia Flash Player,其它的无非是面板不同,或者加入了其它控件,对于计算机上安装的一些播放器也都 ...

  8. 开发那些事儿:在Flv.js前端播放器中解析并绘制H.264编码中的SEI信息

    流媒体中的SEI是指补充增强信息(Supplemental Enhancement Information),它提供了向视频码流中加入信息的办法,是H.264/H.265视频压缩标准的特性之一.SEI ...

  9. 直播播放器API(播放器调用方法、参数、接口和事件)

    直播播放器API(播放器调用方法.参数.接口和事件): 播放器调用方法 <div class="player"></div> <script src= ...

最新文章

  1. AttributeError: Cant get attribute SPPF on module models
  2. Leetcode#43Multiply Strings
  3. 【分布式】分布式事务解决方案概述
  4. sap实施和开发哪个前景_2021年了!还不知道 SAP顾问的职业前景?
  5. 什么是交换机?交换机的工作原理是什么?
  6. 上海2014计算机职称考试理论题,2014年上海市高校计算机一级考试8套理论题含答案...
  7. 求一个数字中1的个数
  8. bootstrap和圣杯布局
  9. 生产环境下,oracle不同用户间的数据迁移。第三部分
  10. 谈谈lucene的DocValues特性之SortedNumericDocValuesField
  11. python︱模块加载(pip安装)以及pycharm安装与报错解决方式
  12. Laya Shader3D之边缘光照
  13. D11:Chickens and Rabbits(鸡兔同笼问题,附题解)
  14. Kubernetes init 提示 [ERROR ImagePull]: failed to pull image registry.aliyuncs.com/google_containers/c
  15. O2O模式发展迅速的原因 o2o模式成功的原因是什么?
  16. Davinci DM6446开发攻略——DSP开发工程建立
  17. 数据恢复如何看异或问题
  18. Python3 使用PIL/Pillow库将图片转换为txt文字云
  19. python在线 培训
  20. Android实现弹出窗口(PopUpWindow)

热门文章

  1. oracle存储过程学习经典[语法+实例+调用]
  2. 作为一个程序员我们如何快乐的学习成长进步呢?(个人感悟和技术无关)
  3. 黑马程序员_Propetties与配置文件
  4. 《简历吐槽大会》——活动相关事宜
  5. 愁没项目?来看看这些人是怎么赚钱的
  6. 一文弄懂动态规划(DP Dynamic Programming)下楼梯,国王和金矿,背包问题,Dijkstra算法
  7. 一文详解图像中的无监督学习
  8. 苹果旧版app_iTunes怎么下载旧版app_iTunes下载旧版app教程
  9. 使用快递100商家寄件API实现寄件功能
  10. 勤学好问的名人名言(分享)