ijkplayer 源碼解析4(視頻解碼+渲染)

ijkplayer源碼解析系列文章

本章主要解析視頻解碼 + 視頻幀刷新邏輯 + 視頻幀繪制 這3個(gè)部分;
第一部分是視頻的解封裝 AVPacket -> AVFrame 線程函數(shù)ffplay_video_thread
第二部分是視頻的刷新邏輯 video_refresh_thread ;
第三個(gè)部分是視頻的渲染層IJKSDLGLView

視頻播放流程圖可以參考如下


視頻解碼播放流程圖.jpg

前情提要,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)系圖如下圖所示


簡(jiǎn)化的關(guān)系圖.jpg

下面的文章以為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ō)的明白的,它太龐大了;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容