ijkplayer源碼解析系列文章
本章主要解析視頻解碼 + 視頻幀刷新邏輯 + 視頻幀繪制 這3個(gè)部分;
第一部分是視頻的解封裝 AVPacket -> AVFrame 線程函數(shù)ffplay_video_thread;
第二部分是視頻的刷新邏輯 video_refresh_thread ;
第三個(gè)部分是視頻的渲染層IJKSDLGLView ;
視頻播放流程圖可以參考如下

前情提要,ijkplayer的解碼線程獨(dú)立于數(shù)據(jù)讀取線程,每個(gè)類型的流(AVStream)都有其對(duì)應(yīng)的解碼線程,如下表所示;
| 類型 | 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 函數(shù)開(kāi)啟相應(yīng)的流和相應(yīng)的解碼線程;音頻流/視頻流/字幕流)也是同樣的邏輯;
視頻解碼函數(shù)調(diào)用堆棧順序如下:
video_thread
ffpipenode_run_sync
func_run_sync
ffp_video_thread
ffplay_video_thread
video_thread 最終調(diào)用到 ffplay_video_thread 函數(shù)主要完成了AVPacket -> AVFrame的功能,該函數(shù)主要功能如下
- 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循環(huán)
for (;;) {
/// 獲取解碼后的視頻幀
ret = get_video_frame(ffp, frame);
if (ret < 0)
goto the_end;
if (!ret)
continue;
if (ffp->get_frame_mode) {
/// 默認(rèn)配置0 ,暫不分析該代碼
///省略代碼。。。
}
#if CONFIG_AVFILTER
///濾鏡代碼、暫不分析
#endif
/// 獲取視頻幀播放時(shí)間
duration = (frame_rate.num && frame_rate.den ? av_q2d((AVRational){frame_rate.den, frame_rate.num}) : 0);
/// 視頻Frame 展示展示時(shí)間pts
pts = (frame->pts == AV_NOPTS_VALUE) ? NAN : frame->pts * av_q2d(tb);
/// 入隊(duì)列操作
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.視頻渲染線程
視頻渲染線程調(diào)用堆棧如下:
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))
/// 刷新視頻 并獲取等待時(shí)間
video_refresh(ffp, &remaining_time);
}
return 0;
}
video_refresh 函數(shù)分析
在分析 video_refresh()函數(shù)的時(shí)候,先來(lái)解釋一下這個(gè)函數(shù)中用到的一些變量和方法 以方便大家理解;
Frame *vp, *lastvp 分別表示為 當(dāng)前幀 和 上一幀;
is->frame_timer 表示當(dāng)前窗口正在顯示的幀的播放時(shí)刻 (單位s)
last_duration 表示上一幀播放時(shí)間
delay 表示當(dāng)前幀需要播放的時(shí)間
av_gettime_relative() 表示獲取系統(tǒng)時(shí)間函數(shù)
frame_queue_nb_remaining() 表示獲取當(dāng)前隊(duì)列frame 數(shù)量
frame_queue_next() 表示獲取隊(duì)列頭部的下一幀
ffp->display_disable 表示是否禁用顯示功能
is->force_refresh 表示是否強(qiáng)制刷新 (當(dāng)下一幀可播放,或者視頻size發(fā)生變化的時(shí)候?yàn)? )
is->show_mode 默認(rèn)為SHOW_MODE_VIDEO表示顯示視頻畫(huà)面,
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) {
/// 隊(duì)列中沒(méi)有視頻幀 什么也不做
} else {
double last_duration, duration, delay;
Frame *vp, *lastvp;
/* dequeue the picture */
/// 獲取上一幀
lastvp = frame_queue_peek_last(&is->pictq);
/// 獲取準(zhǔn)備播放的當(dāng)前幀
vp = frame_queue_peek(&is->pictq);
if (vp->serial != is->videoq.serial) {
frame_queue_next(&is->pictq);
goto retry;
}
if (lastvp->serial != vp->serial)
/// 當(dāng)seek或者快進(jìn)、快退的情況重新賦值 frame_time 時(shí)間
is->frame_timer = av_gettime_relative() / 1000000.0;
if (is->paused)
/// 暫停的情況重復(fù)顯示上一幀
goto display;
/* compute nominal last_duration */
/// last_duration 表示上一幀播放時(shí)間
last_duration = vp_duration(is, lastvp, vp);
/// delay 表示當(dāng)前幀需要播放的時(shí)間
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 不準(zhǔn)的情況下更新
is->frame_timer = time;
if (time < is->frame_timer + delay) {
/// 即將播放的幀+播放時(shí)長(zhǎng) 大于 當(dāng)前時(shí)間,則可以播放,跳轉(zhuǎn)到display播放
*remaining_time = FFMIN(is->frame_timer + delay - time, *remaining_time);
goto display;
}
/// 更新 is->frame_timer時(shí)間
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))
/// 更新視頻時(shí)鐘
update_video_pts(is, vp->pts, vp->pos, vp->serial);
SDL_UnlockMutex(is->pictq.mutex);
if (frame_queue_nb_remaining(&is->pictq) > 1) {
/// 隊(duì)列視頻幀>1 的情況 當(dāng)前幀展示時(shí)間 大于當(dāng)前時(shí)間,則丟掉該幀
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 */
/// 渲染開(kāi)啟、force_refresh ==1 、show_mode 默認(rèn)為 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 默認(rèn)為 0,這部分邏輯不分析
省略部分代碼...
}
}
3.Opengl ES 繪制視頻幀
看了一下,視頻圖像的繪制分支還是蠻多的,其中需要有一定的圖形圖像渲染的基礎(chǔ)知識(shí),否則啃起來(lái)會(huì)比較吃力;
沒(méi)有基礎(chǔ)的可以去看一下我的 Opengl ES 系列文章
OpenGLES - 繪制三角形
OpenGLES - 繪制圖片
OpenGLES - 圖片翻轉(zhuǎn)
OpenGLES - 繪制金字塔
OpenGLES - YUV格式視頻繪制
OpenGLES - RGB格式視頻繪制
從上一章節(jié)的介紹中,我們知道播放器初始化的時(shí)候創(chuàng)建了 實(shí)例,在的方法調(diào)用中將解碼后的視頻數(shù)據(jù)送到了渲染層;
介紹一下渲染中用的 Class ,極其一些渲染相關(guān)的文件是干嘛的吧;
IJKSDLGLView 渲染層View,內(nèi)部通過(guò) CAEAGLLayer實(shí)現(xiàn);SDL_VoutOverlay 視頻幀中間層,內(nèi)部存放著渲染一個(gè)視頻所需要的數(shù)據(jù)
renderer_rgb.c 渲染rgb 類型的視頻幀的實(shí)現(xiàn)邏輯
renderer_yuv420p.c 渲染YUV420P 類型的視頻幀的實(shí)現(xiàn)邏輯
renderer_yuv420sp_vtb.m 渲染YUV420P_VTB 類型的視頻幀的實(shí)現(xiàn)邏輯
renderer_yuv420sp.c
renderer_yuv444p10le.c
簡(jiǎn)化的關(guān)系圖如下圖所示

下面的文章以為yuv420sp 類型的視頻幀繪制為例子,來(lái)做分析;
3.1 IJKSDLGLView 介紹
Opengl環(huán)境初始化
EAGLLayer 的初始化沒(méi)什么特別好講的,都是模版套路;配置一下透明度,縮放比例
/// 初始化opengGL 上下文及其相關(guān)參數(shù)
[self setupGLOnce];
初始化相關(guān)類型Render
/// 檢查render 是否存在,和當(dāng)前渲染的overlayer 格式是否一致,不一致的情況,重新創(chuàng)建
if (!IJK_GLES2_Renderer_isValid(_renderer) ||
!IJK_GLES2_Renderer_isFormat(_renderer, overlay->format)) {
/// 重置并釋放掉老的_render實(shí)例
IJK_GLES2_Renderer_reset(_renderer);
IJK_GLES2_Renderer_freeP(&_renderer);
/// 根據(jù)overlayer 類型創(chuàng)建 相應(yīng)的render實(shí)例
_renderer = IJK_GLES2_Renderer_create(overlay);
if (!IJK_GLES2_Renderer_isValid(_renderer))
return NO;
if (!IJK_GLES2_Renderer_use(_renderer))
return NO;
//_rendererGravity 參數(shù)決定畫(huà)面的展示模式/ 填充/適應(yīng)/裁剪填充 的模式
IJK_GLES2_Renderer_setGravity(_renderer, _rendererGravity, _backingWidth, _backingHeight);
}
3.2 IJK_GLES2_Renderer 解析
IJK_GLES2_Renderer 封裝了繪制視頻幀的操作邏輯;
-
IJK_GLES2_Renderer_reset//重置Render 內(nèi)的所有參數(shù) -
IJK_GLES2_Renderer_free//釋放Render 結(jié)構(gòu)體 -
IJK_GLES2_Renderer_freeP//釋放Render指針 -
IJK_GLES2_Renderer_create_base//通過(guò)片源著色器創(chuàng)建特定類型Render -
IJK_GLES2_Renderer_create//通過(guò)overlayer創(chuàng)建Render 的實(shí)現(xiàn)方法 -
IJK_GLES2_Renderer_isValid//驗(yàn)證Render program 程序是否可用 -
IJK_GLES2_Renderer_setupGLES//Render 初始化OpenGL ES 配置 -
IJK_GLES2_Renderer_Vertices_reset//頂點(diǎn)數(shù)據(jù)重置 -
IJK_GLES2_Renderer_Vertices_apply//根據(jù)ASPECT 配置計(jì)算后生成新的頂點(diǎn)數(shù)據(jù) -
IJK_GLES2_Renderer_Vertices_reloadVertex//更新定點(diǎn)數(shù)據(jù) -
IJK_GLES2_Renderer_setGravity//ASPECT 配置方法 -
IJK_GLES2_Renderer_TexCoords_reset//重置紋理坐標(biāo) -
IJK_GLES2_Renderer_TexCoords_cropRight// -
IJK_GLES2_Renderer_use// -
IJK_GLES2_Renderer_renderOverlay//
Render的創(chuàng)建,根據(jù)overlay的視頻幀格式創(chuàng)建不同的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 解析
創(chuàng)建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 紋理標(biāo)識(shí)符 存入 us2_sampler[0] 中
renderer->us2_sampler[0] = glGetUniformLocation(renderer->program, "us2_SamplerX"); IJK_GLES2_checkError_TRACE("glGetUniformLocation(us2_SamplerX)");
/// 從programe中獲取 UV 紋理標(biāo)識(shí)符 存入 us2_sampler[1] 中
renderer->us2_sampler[1] = glGetUniformLocation(renderer->program, "us2_SamplerY"); IJK_GLES2_checkError_TRACE("glGetUniformLocation(us2_SamplerY)");
/// 從programe中獲取 顏色矩陣標(biāo)識(shí)符
renderer->um3_color_conversion = glGetUniformLocation(renderer->program, "um3_ColorConversion"); IJK_GLES2_checkError_TRACE("glGetUniformLocation(um3_ColorConversionMatrix)");
/// 函數(shù)指針指向YUV420sp 相應(yīng)函數(shù)的實(shí)現(xiàn)
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;
}
將視頻幀的圖像數(shù)據(jù)傳入FrameBuffer 中
static GLboolean yuv420sp_uploadTexture(IJK_GLES2_Renderer *renderer, SDL_VoutOverlay *overlay)
{
if (!renderer || !overlay)
return GL_FALSE;
/// 獲取圖像的width、height、圖像數(shù)據(jù)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 數(shù)據(jù)到相應(yīng)的紋理空間
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 數(shù)據(jù)到相應(yīng)的紋理空間
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;
}
激活紋理,并設(shè)置紋理過(guò)濾參數(shù),最終顯示圖像;
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的視頻解碼和渲染分析完了,渲染這塊寫(xiě)的不是很詳細(xì),后續(xù)有時(shí)間再來(lái)修改,因?yàn)?code>OpenGL這塊真是不是一個(gè)章節(jié)就能說(shuō)的明白的,它太龐大了;