ijkplayer
源码解析系列文章
本章主要解析视频解码
+ 视频帧刷新逻辑
+ 视频帧绘制
这3个部分;
第一部分是视频的解封装 AVPacket -> AVFrame
线程函数ffplay_video_thread
;
第二部分是视频的刷新逻辑 video_refresh_thread
;
第三个部分是视频的渲染层IJKSDLGLView
;
视频播放流程图可以参考如下
前情提要,ijkplayer
的解码线程独立于数据读取线程,每个类型的流(AVStream)
都有其对应的解码线程,如下表所示;
类型 | PacketQueue | FrameQueue | clock | Decoder | 解码线程 |
---|---|---|---|---|---|
音频 | audioq | sampq | audclk | auddec | audio_thread |
视频 | videoq | pictq | vidclk | viddec | video_thread |
字幕 | subtitleq | sampq | ---- | subdec | subtitle_thread |
1.视频解码线程
在read_thread
后,获取视频流的stramIndex
,使用 stream_component_open
函数开启相应的流和相应的解码线程;音频流/视频流/字幕流)也是同样的逻辑;
视频解码函数调用堆栈顺序如下:
video_thread
ffpipenode_run_sync
func_run_sync
ffp_video_thread
ffplay_video_thread
video_thread
最终调用到 ffplay_video_thread
函数主要完成了AVPacket -> AVFrame
的功能,该函数主要功能如下
- 1.1 获取解码后的视频并送入
视频FrameQueue
1.1 获取解码后的视频并送入 FrameQueue
static int ffplay_video_thread(void *arg)
{
FFPlayer *ffp = arg;
VideoState *is = ffp->is;
/// 初始化frame
AVFrame *frame = av_frame_alloc();
double pts;
double duration;
int ret;
AVRational tb = is->video_st->time_base;
AVRational frame_rate = av_guess_frame_rate(is->ic, is->video_st, NULL);
//省略部分代码...
#if CONFIG_AVFILTER
///滤镜代码、暂不分析
#else
ffp_notify_msg2(ffp, FFP_MSG_VIDEO_ROTATION_CHANGED, ffp_get_video_rotate_degrees(ffp));
#endif
if (!frame) {
#if CONFIG_AVFILTER
///滤镜代码、暂不分析
#endif
return AVERROR(ENOMEM);
}
/// 获取视频Frame循环
for (;;) {
/// 获取解码后的视频帧
ret = get_video_frame(ffp, frame);
if (ret < 0)
goto the_end;
if (!ret)
continue;
if (ffp->get_frame_mode) {
/// 默认配置0 ,暂不分析该代码
///省略代码。。。
}
#if CONFIG_AVFILTER
///滤镜代码、暂不分析
#endif
/// 获取视频帧播放时间
duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational){frame_rate.den, frame_rate.num}) : 0);
/// 视频Frame 展示展示时间pts
pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);
/// 入队列操作
ret = queue_picture(ffp, frame, pts, duration, frame->pkt_pos, is->viddec.pkt_serial);
/// 释放引用
av_frame_unref(frame);
#if CONFIG_AVFILTER
}
#endif
if (ret < 0)
goto the_end;
}
the_end:
#if CONFIG_AVFILTER
///滤镜代码、暂不分析
#endif
av_log(NULL, AV_LOG_INFO, "convert image convert_frame_count = %d\n", convert_frame_count);
av_frame_free(&frame);
return 0;
}
2.视频渲染线程
视频渲染线程调用堆栈如下:
video_refresh_thread
video_refresh
video_display2
video_image_display2
SDL_VoutDisplayYUVOverlay
vout_display_overlay
vout_display_overlay_l
static int video_refresh_thread(void *arg)
{
/// 播放器ffp
FFPlayer *ffp = arg;
VideoState *is = ffp->is;
double remaining_time = 0.0;
/// 再非中断的情况下
while (!is->abort_request) {
if (remaining_time > 0.0)
av_usleep((int)(int64_t)(remaining_time * 1000000.0));
remaining_time = REFRESH_RATE;
if (is->show_mode != SHOW_MODE_NONE && (!is->paused || is->force_refresh))
/// 刷新视频 并获取等待时间
video_refresh(ffp, &remaining_time);
}
return 0;
}
video_refresh 函数分析
在分析 video_refresh()
函数的时候,先来解释一下这个函数中用到的一些变量和方法 以方便大家理解;
Frame *vp, *lastvp
分别表示为 当前帧 和 上一帧;
is->frame_timer
表示当前窗口正在显示的帧的播放时刻 (单位s)
last_duration
表示上一帧播放时间
delay
表示当前帧需要播放的时间
av_gettime_relative()
表示获取系统时间函数
frame_queue_nb_remaining()
表示获取当前队列frame 数量
frame_queue_next()
表示获取队列头部的下一帧
ffp->display_disable
表示是否禁用显示功能
is->force_refresh
表示是否强制刷新 (当下一帧可播放,或者视频size发生变化的时候为1 )
is->show_mode
默认为SHOW_MODE_VIDEO
表示显示视频画面,
static void video_refresh(FFPlayer *opaque, double *remaining_time)
{
FFPlayer *ffp = opaque;
VideoState *is = ffp->is;
double time;
Frame *sp, *sp2;
if (!is->paused && get_master_sync_type(is) == AV_SYNC_EXTERNAL_CLOCK && is->realtime)
check_external_clock_speed(is);
if (!ffp->display_disable && is->show_mode != SHOW_MODE_VIDEO && is->audio_st) {
time = av_gettime_relative() / 1000000.0;
if (is->force_refresh || is->last_vis_time + ffp->rdftspeed < time) {
video_display2(ffp);
is->last_vis_time = time;
}
*remaining_time = FFMIN(*remaining_time, is->last_vis_time + ffp->rdftspeed - time);
}
/// 判断视频流存在
if (is->video_st) {
retry:
if (frame_queue_nb_remaining(&is->pictq) == 0) {
/// 队列中没有视频帧 什么也不做
} else {
double last_duration, duration, delay;
Frame *vp, *lastvp;
/* dequeue the picture */
/// 获取上一帧
lastvp = frame_queue_peek_last(&is->pictq);
/// 获取准备播放的当前帧
vp = frame_queue_peek(&is->pictq);
if (vp->serial != is->videoq.serial) {
frame_queue_next(&is->pictq);
goto retry;
}
if (lastvp->serial != vp->serial)
/// 当seek或者快进、快退的情况重新赋值 frame_time 时间
is->frame_timer = av_gettime_relative() / 1000000.0;
if (is->paused)
/// 暂停的情况重复显示上一帧
goto display;
/* compute nominal last_duration */
/// last_duration 表示上一帧播放时间
last_duration = vp_duration(is, lastvp, vp);
/// delay 表示当前帧需要播放的时间
delay = compute_target_delay(ffp, last_duration, is);
time= av_gettime_relative()/1000000.0;
if (isnan(is->frame_timer) || time < is->frame_timer)
/// is->frame_timer 不准的情况下更新
is->frame_timer = time;
if (time < is->frame_timer + delay) {
/// 即将播放的帧+播放时长 大于 当前时间,则可以播放,跳转到display播放
*remaining_time = FFMIN(is->frame_timer + delay - time, *remaining_time);
goto display;
}
/// 更新 is->frame_timer时间
is->frame_timer += delay;
if (delay > 0 && time - is->frame_timer > AV_SYNC_THRESHOLD_MAX)
is->frame_timer = time;
SDL_LockMutex(is->pictq.mutex);
if (!isnan(vp->pts))
/// 更新视频时钟
update_video_pts(is, vp->pts, vp->pos, vp->serial);
SDL_UnlockMutex(is->pictq.mutex);
if (frame_queue_nb_remaining(&is->pictq) > 1) {
/// 队列视频帧>1 的情况 当前帧展示时间 大于当前时间,则丢掉该帧
Frame *nextvp = frame_queue_peek_next(&is->pictq);
duration = vp_duration(is, vp, nextvp);
if(!is->step && (ffp->framedrop > 0 || (ffp->framedrop && get_master_sync_type(is) != AV_SYNC_VIDEO_MASTER)) && time > is->frame_timer + duration) {
frame_queue_next(&is->pictq);
goto retry;
}
}
if (is->subtitle_st) {
/// 视频字幕流的情况 暂不分析
while (frame_queue_nb_remaining(&is->subpq) > 0) {
sp = frame_queue_peek(&is->subpq);
if (frame_queue_nb_remaining(&is->subpq) > 1)
sp2 = frame_queue_peek_next(&is->subpq);
else
sp2 = NULL;
if (sp->serial != is->subtitleq.serial
|| (is->vidclk.pts > (sp->pts + ((float) sp->sub.end_display_time / 1000)))
|| (sp2 && is->vidclk.pts > (sp2->pts + ((float) sp2->sub.start_display_time / 1000))))
{
if (sp->uploaded) {
ffp_notify_msg4(ffp, FFP_MSG_TIMED_TEXT, 0, 0, "", 1);
}
frame_queue_next(&is->subpq);
} else {
break;
}
}
}
frame_queue_next(&is->pictq);
is->force_refresh = 1;
SDL_LockMutex(ffp->is->play_mutex);
if (is->step) {
is->step = 0;
if (!is->paused)
stream_update_pause_l(ffp);
}
SDL_UnlockMutex(ffp->is->play_mutex);
}
display:
/* display picture */
/// 渲染开启、force_refresh ==1 、show_mode 默认为 SHOW_MODE_VIDEO 的情况渲染
if (!ffp->display_disable && is->force_refresh && is->show_mode == SHOW_MODE_VIDEO && is->pictq.rindex_shown)
video_display2(ffp);
}
is->force_refresh = 0;
if (ffp->show_status) {
/// show_status 默认为 0,这部分逻辑不分析
省略部分代码...
}
}
3.Opengl ES 绘制视频帧
看了一下,视频图像的绘制分支还是蛮多的,其中需要有一定的图形图像渲染的基础知识,否则啃起来会比较吃力;
没有基础的可以去看一下我的 Opengl ES
系列文章
OpenGLES - 绘制三角形
OpenGLES - 绘制图片
OpenGLES - 图片翻转
OpenGLES - 绘制金字塔
OpenGLES - YUV格式视频绘制
OpenGLES - RGB格式视频绘制
从上一章节的介绍中,我们知道播放器初始化的时候创建了 实例,在的方法调用中将解码后的视频数据送到了渲染层;
介绍一下渲染中用的 Class
,极其一些渲染相关的文件是干嘛的吧;
IJKSDLGLView
渲染层View
,内部通过 CAEAGLLayer
实现;SDL_VoutOverlay
视频帧中间层,内部存放着渲染一个视频所需要的数据
renderer_rgb.c
渲染rgb
类型的视频帧的实现逻辑
renderer_yuv420p.c
渲染YUV420P
类型的视频帧的实现逻辑
renderer_yuv420sp_vtb.m
渲染YUV420P_VTB
类型的视频帧的实现逻辑
renderer_yuv420sp.c
renderer_yuv444p10le.c
简化的关系图如下图所示
下面的文章以为yuv420sp
类型的视频帧绘制为例子,来做分析;
3.1 IJKSDLGLView 介绍
Opengl
环境初始化
EAGLLayer 的初始化没什么特别好讲的,都是模版套路;配置一下透明度,缩放比例
/// 初始化opengGL 上下文及其相关参数
[self setupGLOnce];
初始化相关类型Render
/// 检查render 是否存在,和当前渲染的overlayer 格式是否一致,不一致的情况,重新创建
if (!IJK_GLES2_Renderer_isValid(_renderer) ||
!IJK_GLES2_Renderer_isFormat(_renderer, overlay->format)) {
/// 重置并释放掉老的_render实例
IJK_GLES2_Renderer_reset(_renderer);
IJK_GLES2_Renderer_freeP(&_renderer);
/// 根据overlayer 类型创建 相应的render实例
_renderer = IJK_GLES2_Renderer_create(overlay);
if (!IJK_GLES2_Renderer_isValid(_renderer))
return NO;
if (!IJK_GLES2_Renderer_use(_renderer))
return NO;
//_rendererGravity 参数决定画面的展示模式/ 填充/适应/裁剪填充 的模式
IJK_GLES2_Renderer_setGravity(_renderer, _rendererGravity, _backingWidth, _backingHeight);
}
3.2 IJK_GLES2_Renderer 解析
IJK_GLES2_Renderer
封装了绘制视频帧的操作逻辑;
-
IJK_GLES2_Renderer_reset
//重置Render 内的所有参数 -
IJK_GLES2_Renderer_free
//释放Render 结构体 -
IJK_GLES2_Renderer_freeP
//释放Render指针 -
IJK_GLES2_Renderer_create_base
//通过片源着色器创建特定类型Render -
IJK_GLES2_Renderer_create
//通过overlayer创建Render 的实现方法 -
IJK_GLES2_Renderer_isValid
//验证Render program 程序是否可用 -
IJK_GLES2_Renderer_setupGLES
//Render 初始化OpenGL ES 配置 -
IJK_GLES2_Renderer_Vertices_reset
//顶点数据重置 -
IJK_GLES2_Renderer_Vertices_apply
//根据ASPECT 配置计算后生成新的顶点数据 -
IJK_GLES2_Renderer_Vertices_reloadVertex
//更新定点数据 -
IJK_GLES2_Renderer_setGravity
//ASPECT 配置方法 -
IJK_GLES2_Renderer_TexCoords_reset
//重置纹理坐标 -
IJK_GLES2_Renderer_TexCoords_cropRight
// -
IJK_GLES2_Renderer_use
// -
IJK_GLES2_Renderer_renderOverlay
//
Render的创建,根据overlay
的视频帧格式创建不同的render
IJK_GLES2_Renderer *IJK_GLES2_Renderer_create(SDL_VoutOverlay *overlay)
{
if (!overlay)
return NULL;
省略代码...
IJK_GLES2_Renderer *renderer = NULL;
switch (overlay->format) {
case SDL_FCC_RV16: renderer = IJK_GLES2_Renderer_create_rgb565(); break;
case SDL_FCC_RV24: renderer = IJK_GLES2_Renderer_create_rgb888(); break;
case SDL_FCC_RV32: renderer = IJK_GLES2_Renderer_create_rgbx8888(); break;
#ifdef __APPLE__
case SDL_FCC_NV12: renderer = IJK_GLES2_Renderer_create_yuv420sp(); break;
case SDL_FCC__VTB: renderer = IJK_GLES2_Renderer_create_yuv420sp_vtb(overlay); break;
#endif
case SDL_FCC_YV12: renderer = IJK_GLES2_Renderer_create_yuv420p(); break;
case SDL_FCC_I420: renderer = IJK_GLES2_Renderer_create_yuv420p(); break;
case SDL_FCC_I444P10LE: renderer = IJK_GLES2_Renderer_create_yuv444p10le(); break;
default:
ALOGE("[GLES2] unknown format %4s(%d)\n", (char *)&overlay->format, overlay->format);
return NULL;
}
renderer->format = overlay->format;
return renderer;
}
3.3 yuv420sp 类型 IJK_GLES2_Renderer 解析
创建yuv420sp
类型Render
IJK_GLES2_Renderer *IJK_GLES2_Renderer_create_yuv420sp()
{
IJK_GLES2_Renderer *renderer = IJK_GLES2_Renderer_create_base(IJK_GLES2_getFragmentShader_yuv420sp());
if (!renderer)
goto fail;
/// 从programe中获取 Y 纹理标识符 存入 us2_sampler[0] 中
renderer->us2_sampler[0] = glGetUniformLocation(renderer->program, "us2_SamplerX"); IJK_GLES2_checkError_TRACE("glGetUniformLocation(us2_SamplerX)");
/// 从programe中获取 UV 纹理标识符 存入 us2_sampler[1] 中
renderer->us2_sampler[1] = glGetUniformLocation(renderer->program, "us2_SamplerY"); IJK_GLES2_checkError_TRACE("glGetUniformLocation(us2_SamplerY)");
/// 从programe中获取 颜色矩阵标识符
renderer->um3_color_conversion = glGetUniformLocation(renderer->program, "um3_ColorConversion"); IJK_GLES2_checkError_TRACE("glGetUniformLocation(um3_ColorConversionMatrix)");
/// 函数指针指向YUV420sp 相应函数的实现
renderer->func_use = yuv420sp_use;
renderer->func_getBufferWidth = yuv420sp_getBufferWidth;
renderer->func_uploadTexture = yuv420sp_uploadTexture;
return renderer;
fail:
IJK_GLES2_Renderer_free(renderer);
return NULL;
}
将视频帧的图像数据传入FrameBuffer
中
static GLboolean yuv420sp_uploadTexture(IJK_GLES2_Renderer *renderer, SDL_VoutOverlay *overlay)
{
if (!renderer || !overlay)
return GL_FALSE;
/// 获取图像的width、height、图像数据byte存放地址
const GLsizei widths[2] = { overlay->pitches[0], overlay->pitches[1] / 2 };
const GLsizei heights[2] = { overlay->h, overlay->h / 2 };
const GLubyte *pixels[2] = { overlay->pixels[0], overlay->pixels[1] };
switch (overlay->format) {
case SDL_FCC__VTB:
break;
default:
ALOGE("[yuv420sp] unexpected format %x\n", overlay->format);
return GL_FALSE;
}
/// 绑定Y纹理,传入 Y 数据到相应的纹理空间
glBindTexture(GL_TEXTURE_2D, renderer->plane_textures[0]);
glTexImage2D(GL_TEXTURE_2D,
0,
GL_RED_EXT,
widths[0],
heights[0],
0,
GL_RED_EXT,
GL_UNSIGNED_BYTE,
pixels[0]);
/// 绑定UV纹理,传入 UV 数据到相应的纹理空间
glBindTexture(GL_TEXTURE_2D, renderer->plane_textures[1]);
glTexImage2D(GL_TEXTURE_2D,
0,
GL_RG_EXT,
widths[1],
heights[1],
0,
GL_RG_EXT,
GL_UNSIGNED_BYTE,
pixels[1]);
return GL_TRUE;
}
激活纹理,并设置纹理过滤参数,最终显示图像;
static GLboolean yuv420sp_use(IJK_GLES2_Renderer *renderer)
{
ALOGI("use render yuv420sp\n");
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glUseProgram(renderer->program); IJK_GLES2_checkError_TRACE("glUseProgram");
if (0 == renderer->plane_textures[0])
glGenTextures(2, renderer->plane_textures);
for (int i = 0; i < 2; ++i) {
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_2D, renderer->plane_textures[i]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glUniform1i(renderer->us2_sampler[i], i);
}
glUniformMatrix3fv(renderer->um3_color_conversion, 1, GL_FALSE, IJK_GLES2_getColorMatrix_bt709());
return GL_TRUE;
}
至此,ijkplayer
的视频解码和渲染分析完了,渲染这块写的不是很详细,后续有时间再来修改,因为OpenGL
这块真是不是一个章节就能说的明白的,它太庞大了;