【十】【vlc-anroid】视频图像display展示层模块源码分析-SurfaceView交互渲染

由此前分析过的vlc组件模块加载方式可知,display模块也是类似方式。而视频图像展示方式在vlc-android端可分析两种一种是OpenGL渲染,另一种则是SurfaceView这种原生Android API展示。
此篇分析SurfaceView展示方式。
1、由第八章【2.2小节分析】中对应的display模块组件加载创建调用方法待分析,如下:

// [vlc/src/video_output/vout_wrapper.c]
int vout_OpenWrapper(vout_thread_t *vout,
                     const char *splitter_name, const vout_display_state_t *state)
{
    
    
    vout_thread_sys_t *sys = vout->p;
    msg_Dbg(vout, "Opening vout display wrapper");

    // 获取视频名称
    /* */
    sys->display.title = var_InheritString(vout, "video-title");

    // 初始化鼠标双击事件超时时间(默认300毫秒)和隐藏鼠标超时时间
    // 这两个事件不适用于android端
    /* */
    const mtime_t double_click_timeout = 300000;
    const mtime_t hide_timeout = var_CreateGetInteger(vout, "mouse-hide-timeout") * 1000;

    if (splitter_name) {
    
    
        // 此为视频编辑/剪切display显示模块加载,暂时不分析该情况
        sys->display.vd = vout_NewSplitter(vout, &vout->p->original, state, "$vout", splitter_name,
                                           double_click_timeout, hide_timeout);
    } else {
    
    
        // 分析display显示模块直接加载方式
        // 见1.1小节分析
        sys->display.vd = vout_NewDisplay(vout, &vout->p->original, state, "$vout",
                                          double_click_timeout, hide_timeout);
    }
    if (!sys->display.vd) {
    
    
        free(sys->display.title);
        return VLC_EGENERIC;
    }

    /* */
#ifdef _WIN32
    // 此处为windows的墙窗口页模式即分屏显示模式
    var_Create(vout, "video-wallpaper", VLC_VAR_BOOL|VLC_VAR_DOINHERIT);
    var_AddCallback(vout, "video-wallpaper", Forward, NULL);
#endif

    /* */
    sys->decoder_pool = NULL;

    return VLC_SUCCESS;
}

1.1、vout_NewDisplay实现分析:【vlc/src/video_output/display.c】

vout_display_t *vout_NewDisplay(vout_thread_t *vout,
                                const video_format_t *source,
                                const vout_display_state_t *state,
                                const char *module,
                                mtime_t double_click_timeout,
                                mtime_t hide_timeout)
{
    
    
    return DisplayNew(vout, source, state, module, false,
                      double_click_timeout, hide_timeout, NULL);
}

static vout_display_t *DisplayNew(vout_thread_t *vout,
                                  const video_format_t *source,
                                  const vout_display_state_t *state,
                                  const char *module, bool is_splitter,
                                  mtime_t double_click_timeout,
                                  mtime_t hide_timeout,
                                  const vout_display_owner_t *owner_ptr)
{
    
    
    /* */
    vout_display_owner_sys_t *osys = calloc(1, sizeof(*osys));
    vout_display_cfg_t *cfg = &osys->cfg;

    // 初始化默认的显示配置信息:如显示宽高等【有缩放设置计算】
    // 计算:视频DAR = SAR * PAR
    *cfg = state->cfg;
    osys->sar_initial = state->sar;
    vout_display_GetDefaultDisplaySize(&cfg->display.width, &cfg->display.height,
                                       source, cfg);

    osys->vout = vout;
    osys->is_splitter = is_splitter;

    vlc_mutex_init(&osys->lock);

    vlc_mouse_Init(&osys->mouse.state);
    osys->mouse.last_moved = mdate();
    osys->mouse.double_click_timeout = double_click_timeout;
    osys->mouse.hide_timeout = hide_timeout;
    osys->display_width  = cfg->display.width;
    osys->display_height = cfg->display.height;
    osys->is_display_filled = cfg->is_display_filled;
    osys->viewpoint      = cfg->viewpoint;

    // 缩放宽高比例参数设置
    osys->zoom.num = cfg->zoom.num;
    osys->zoom.den = cfg->zoom.den;
#if defined(_WIN32) || defined(__OS2__)
    osys->is_fullscreen  = cfg->is_fullscreen;
    osys->width_saved    = cfg->display.width;
    osys->height_saved   = cfg->display.height;
    if (osys->is_fullscreen) {
    
    
        vout_display_cfg_t cfg_windowed = *cfg;
        cfg_windowed.is_fullscreen  = false;
        cfg_windowed.display.width  = 0;
        cfg_windowed.display.height = 0;
        vout_display_GetDefaultDisplaySize(&osys->width_saved,
                                           &osys->height_saved,
                                           source, &cfg_windowed);
    }

    osys->wm_state_initial = VOUT_WINDOW_STATE_NORMAL;
    osys->wm_state = state->wm_state;
    osys->ch_wm_state = true;
#endif
    osys->fit_window = 0;

    osys->source = *source;
    // 视频剪切参数初始化
    osys->crop.left   = 0;
    osys->crop.top    = 0;
    osys->crop.right  = 0;
    osys->crop.bottom = 0;
    osys->crop.num = 0;
    osys->crop.den = 0;

    // 采样宽高比【即视频分辨率】, DAR为视频显示分辨率
    osys->sar.num = osys->sar_initial.num ? osys->sar_initial.num : source->i_sar_num;
    osys->sar.den = osys->sar_initial.den ? osys->sar_initial.den : source->i_sar_den;

    vout_display_owner_t owner;
    if (owner_ptr) {
    
    
        owner = *owner_ptr;
    } else {
    
    
        // 赋值display展示层方法调用指针
        owner.event      = VoutDisplayEvent;
        owner.window_new = VoutDisplayNewWindow;
        owner.window_del = VoutDisplayDelWindow;
    }
    owner.sys = osys;

    // 创建display展示层模块对象
    // 见1.1.1小节分析
    vout_display_t *p_display = vout_display_New(VLC_OBJECT(vout),
                                                 module, !is_splitter,
                                                 source, cfg, &owner);
    if (!p_display)
        goto error;

    // display展示模块创建render渲染功能【初始化视频滤镜器等】
    if (VoutDisplayCreateRender(p_display)) {
    
    
        vout_display_Delete(p_display);
        goto error;
    }

    /* Setup delayed request */
    if (osys->sar.num != source->i_sar_num ||
        osys->sar.den != source->i_sar_den)
        osys->ch_sar = true;

    vout_SendEventViewpointChangeable(osys->vout,
        p_display->fmt.projection_mode != PROJECTION_MODE_RECTANGULAR);

    return p_display;
error:
    vlc_mutex_destroy(&osys->lock);
    free(osys);
    return NULL;
}

1.1.1、vout_display_New实现分析:【vlc/src/video_output/display.c】

/**
 * It creates a new vout_display_t using the given configuration.
 */
static vout_display_t *vout_display_New(vlc_object_t *obj,
                                        const char *module, bool load_module,
                                        const video_format_t *fmt,
                                        const vout_display_cfg_t *cfg,
                                        vout_display_owner_t *owner)
{
    
    
    /* */
    vout_display_t *vd = vlc_custom_create(obj, sizeof(*vd), "vout display" );

    // 初始化展示层模块的视频源原格式【不可更改】
    /* */
    video_format_Copy(&vd->source, fmt);

    // 初始化展示层模块图像格式【可改变】
    /* Picture buffer does not have the concept of aspect ratio */
    video_format_Copy(&vd->fmt, fmt);
    vd->fmt.i_sar_num = 0;
    vd->fmt.i_sar_den = 0;

    vd->info.is_slow = false;
    vd->info.has_double_click = false;
    vd->info.needs_hide_mouse = false;
    vd->info.has_pictures_invalid = false;
    vd->info.subpicture_chromas = NULL;

    vd->cfg = cfg;
    // 这些(方法指针)赋值通常是在对应的【vout display】组件模块加载初始化执行时赋值的
    vd->pool = NULL;
    vd->prepare = NULL;
    vd->display = NULL;
    vd->control = NULL;
    vd->manage = NULL;
    vd->sys = NULL;

    vd->owner = *owner;

    if (load_module) {
    
    
        // 如果没有启动视频剪辑模块splitter,则加载display展示层模块
        // 见第2小节分析
        vd->module = module_need(vd, "vout display", module, module && *module != '\0');
        if (!vd->module) {
    
    
            vlc_object_release(vd);
            return NULL;
        }
    } else {
    
    
        vd->module = NULL;
    }
    return vd;
}

2、对应的display模块组件初始化如下:
通过全局搜索可关注的display组件模块如下

// 第1种图像输出层展示方式
display.c (vlc\modules\video_output\android) line 63 :     set_capability("vout display", 260)
display.c (vlc\modules\video_output\android) line 69 :         set_capability("vout display", 280)

// 第2种图像输出层展示方式
display.c (vlc\modules\video_output\opengl) line 51 :     set_capability ("vout display", 265)
display.c (vlc\modules\video_output\opengl) line 65 :     set_capability ("vout display", 270)

// 第3种图像输出层展示方式
vout.c (vlc\modules\codec\omxil) line 50 :     set_capability("vout display", 0)

// 第4种图像输出层展示方式【将YUV数据存储到文件中】
yuv.c (vlc\modules\video_output) line 62 :     set_capability("vout display", 0)

从上面的265和260的数值在代码中的意义表示为模块组件的优先级,值越大则优先尝试加载。但本章节分析第1种图像输出层展示方式【安卓视频输出模块组件】,后续再分析其它方式。

// vlc/modules/video_output/android/display.c

vlc_module_begin()
    set_category(CAT_VIDEO)
    set_subcategory(SUBCAT_VIDEO_VOUT)
    set_description("Android video output")
    set_capability("vout display", 260)
    add_shortcut("android-display")
    add_string(CFG_PREFIX "chroma", NULL, CHROMA_TEXT, CHROMA_LONGTEXT, true)
    set_callbacks(Open, Close)
    add_submodule ()
        set_description("Android opaque video output")
        set_capability("vout display", 280)
        add_shortcut("android-opaque")
        set_callbacks(OpenOpaque, Close)
vlc_module_end()

static int Open(vlc_object_t *p_this)
{
    
    
    // 加载该display展示层模块组件时传入的vlc的vout输出层对象
    vout_display_t *vd = (vout_display_t*)p_this;

    // 该情况为使用了【MediaCodec/IOMX】编解码后的不透明的buffer缓冲数据类型
    if (vd->fmt.i_chroma == VLC_CODEC_ANDROID_OPAQUE)
        return VLC_EGENERIC;

    /* At this point, gles2 vout failed (old Android device) */
    vd->fmt.projection_mode = PROJECTION_MODE_RECTANGULAR;
    return OpenCommon(vd);
}

OpenCommon实现分析: 【vlc/modules/video_output/android/display.c】

static int OpenCommon(vout_display_t *vd)
{
    
    
    vout_display_sys_t *sys;
    video_format_t sub_fmt;

    /* Fallback to normal projection in case of soft decoding/display (the
     * openGL vout, with a higher priority, should be used when the projection
     * need to be handled). */
    if (vd->fmt.i_chroma == VLC_CODEC_ANDROID_OPAQUE
     && vd->fmt.projection_mode != PROJECTION_MODE_RECTANGULAR)
        // 该显示方式应该需要OpenGL输出模块来展示
        return VLC_EGENERIC;
    vd->fmt.projection_mode = PROJECTION_MODE_RECTANGULAR;

    // 初始化Android native window对象,并关联java层对应对象信息
    // 见2.1小节分析
    vout_window_t *embed =
        vout_display_NewWindow(vd, VOUT_WINDOW_TYPE_ANDROID_NATIVE);

    if (embed == NULL)
        return VLC_EGENERIC;
    assert(embed->handle.anativewindow);
    // 获取window创建成功后JNI层android window handler处理者对象
    // 即该对象保存了java层和native层的相关Surface对象信息等
    AWindowHandler *p_awh = embed->handle.anativewindow;

    if (!AWindowHandler_canSetVideoLayout(p_awh))
    {
    
    // 如果不能设置改变视频布局,那么强制使用OpenEGL ES2组件模块来处理视频展示
        /* It's better to use gles2 if we are not able to change the video
         * layout */
        vout_display_DeleteWindow(vd, embed);
        return VLC_EGENERIC;
    }

    /* Allocate structure */
    vd->sys = sys = (struct vout_display_sys_t*)calloc(1, sizeof(*sys));
    if (!sys)
    {
    
    
        vout_display_DeleteWindow(vd, embed);
        return VLC_ENOMEM;
    }

    sys->embed = embed;
    sys->p_awh = p_awh;
    // 获取持有android native window的API方法指针【android/native_window.h】的结构体
    // 用于后续方法调用
    sys->anw = AWindowHandler_getANativeWindowAPI(sys->p_awh);

#ifdef USE_ANWP
    // 默认执行此处
    // 此实现为:初始化加载android native window的私有API的方法指针【system/core/include/system/window.h】
    // 加载方式为:从so库中获取当前指定方法指针
    sys->b_has_anwp = android_loadNativeWindowPrivApi(&sys->anwp) == 0;
    if (!sys->b_has_anwp)
        msg_Warn(vd, "Could not initialize NativeWindow Priv API.");
#endif

    // 视频显示宽高
    sys->i_display_width = vd->cfg->display.width;
    sys->i_display_height = vd->cfg->display.height;

    if (vd->fmt.i_chroma != VLC_CODEC_ANDROID_OPAQUE) {
    
    
        // 获取并初始化色彩空间模式
        /* Setup chroma */
        char *psz_fcc = var_InheritString(vd, CFG_PREFIX "chroma");
        if (psz_fcc) {
    
    
            vd->fmt.i_chroma = vlc_fourcc_GetCodecFromString(VIDEO_ES, psz_fcc);
            free(psz_fcc);
        } else
            // 若未设置该变量的值,则默认使用RGB32颜色
            vd->fmt.i_chroma = VLC_CODEC_RGB32;

        switch(vd->fmt.i_chroma) {
    
    
            case VLC_CODEC_YV12:
                // 将YV12色彩编码格式转换为YU12编码格式
                /* avoid swscale usage by asking for I420 instead since the
                 * vout already has code to swap the buffers */
                vd->fmt.i_chroma = VLC_CODEC_I420;
            case VLC_CODEC_I420:
                break;
            case VLC_CODEC_RGB16:
            case VLC_CODEC_RGB32:
            case VLC_CODEC_RGBA:
                // 此处初始化设置RGB掩膜(mask)的值
                // 用一句话总结,掩膜就是两幅图像之间进行的各种位运算操作
                SetRGBMask(&vd->fmt);
                video_format_FixRgb(&vd->fmt);
                break;
            default:
                goto error;
        }
    }

    // 创建android native window对象并根据设置获取native Surface
    // 见2.2小节分析
    sys->p_window = AndroidWindow_New(vd, &vd->fmt, AWindow_Video, true);
    if (!sys->p_window)
        goto error;

    // android本地窗口初始化
    // 见2.3小节分析
    if (AndroidWindow_Setup(sys, sys->p_window, 0) != 0)
        goto error;

    // 若不使用anw【android native window】私有API,则使用软件处理来转换window角度方向等设置
    /* use software rotation if we don't use private anw */
    if (!sys->p_window->b_opaque && !sys->p_window->b_use_priv)
        video_format_TransformTo(&vd->fmt, ORIENT_NORMAL);

    msg_Dbg(vd, "using %s", sys->p_window->b_opaque ? "opaque" :
            (sys->p_window->b_use_priv ? "ANWP" : "ANW"));

    // 初始化字幕子图像格式
    video_format_ApplyRotation(&sub_fmt, &vd->fmt);
    sub_fmt.i_chroma = subpicture_chromas[0];
    SetRGBMask(&sub_fmt);
    video_format_FixRgb(&sub_fmt);
    // 同上video window创建分析
    sys->p_sub_window = AndroidWindow_New(vd, &sub_fmt, AWindow_Subtitles, false);
    if (sys->p_sub_window) {
    
    

        // 修正字幕图像格式
        FixSubtitleFormat(sys);
        sys->i_sub_last_order = -1;

        /* Export the subpicture capability of this vout. */
        vd->info.subpicture_chromas = subpicture_chromas;
    }
    else if (!vd->obj.force && sys->p_window->b_opaque)
    {
    
    
        msg_Warn(vd, "cannot blend subtitles with an opaque surface, "
                     "trying next vout");
        goto error;
    }

    // 初始化display模块交互方法指针
    /* Setup vout_display */
    // 见第3小节分析
    vd->pool    = Pool;
    // 见第4小节分析
    vd->prepare = Prepare;
    // 见第5小节分析
    vd->display = Display;
    // 见第6小节分析
    vd->control = Control;
    // 在此章节分析的Surface交互中,该值初始化为true即含义为【图片存储器读/写速度慢】
    // 其实该意义是默认vlc推荐使用OpenGL来渲染的
    vd->info.is_slow = !sys->p_window->b_opaque;

    return VLC_SUCCESS;

error:
    Close(VLC_OBJECT(vd));
    return VLC_EGENERIC;
}

2.1、vout_display_NewWindow实现分析:

// 【vlc/include/vlc_vout_display.c】
/**
 * Asks for a new window of a given type.
 */
static inline vout_window_t *vout_display_NewWindow(vout_display_t *vd, unsigned type)
{
    
    
    // 调用了display对象创建时的赋值方法【window_new】,见第1小节中的分析
    // [owner.window_new = VoutDisplayNewWindow;]
    return vd->owner.window_new(vd, type);
}

// 【vlc/src/video_output/display.c】
static vout_window_t *VoutDisplayNewWindow(vout_display_t *vd, unsigned type)
{
    
    
    vout_display_owner_sys_t *osys = vd->owner.sys;
    vout_window_t *window = vout_NewDisplayWindow(osys->vout, type);
    if (window != NULL)
        // 关联widow模块和display模块层,见2.1.1小节分析
        vout_display_window_Attach(window, vd);
    return window;
}

// 【vlc/src/video_output/video_output.c】
vout_window_t *vout_NewDisplayWindow(vout_thread_t *vout, unsigned type)
{
    
    
    // 可知该window对象是在vout线程信息对象创建时进行初始化的
    // 通过前面vout章节可知该初始化地方为,第8章第1.1小节中,
    // [vout_window_t *window = vout_display_window_New(vout, &wcfg);]
    // 此小节下面的分析
    vout_window_t *window = vout->p->window;

    assert(vout->p->splitter_name == NULL);

    if (window == NULL)
        return NULL;
    if (type != VOUT_WINDOW_TYPE_INVALID && type != window->type)
        return NULL;
    return window;
}

// 【vlc/src/video_output/window.c】
/**
 * Creates a video window, initially without any attached display.
 */
vout_window_t *vout_display_window_New(vout_thread_t *vout,
                                       const vout_window_cfg_t *cfg)
{
    
    
    vout_display_window_t *state = malloc(sizeof (*state));
    if (state == NULL)
        return NULL;

    // 赋值配置信息
    state->vd = NULL;
    state->width = cfg->width;
    state->height = cfg->height;
    vlc_mutex_init(&state->lock);

    vout_window_owner_t owner = {
    
    
        .sys = state,
        .resized = vout_display_window_ResizeNotify,
        .closed = vout_display_window_CloseNotify,
        .mouse_event = vout_display_window_MouseEvent,
    };
    vout_window_t *window;

    // 创建新window对象
    window = vout_window_New((vlc_object_t *)vout, "$window", cfg, &owner);
    if (window == NULL) {
    
    
        vlc_mutex_destroy(&state->lock);
        free(state);
    }
    return window;
}

// 【vlc/src/video_output/window.c】
vout_window_t *vout_window_New(vlc_object_t *obj, const char *module,
                               const vout_window_cfg_t *cfg,
                               const vout_window_owner_t *owner)
{
    
    
    window_t *w = vlc_custom_create(obj, sizeof(*w), "window");
    vout_window_t *window = &w->wnd;

    if (owner != NULL)
        window->owner = *owner;
    else
        window->owner.resized = NULL;

    // 加载一个window组件模块
    // 通过模块组件搜索可知android设备上加载window如下:
    // window.c (vlc\modules\video_output\android) line 54 :    
    // set_capability("vout window", 10)
    // 见第十一章节分析
    w->module = vlc_module_load(window, "vout window", module,
                                module && *module,
                                vout_window_start, window, cfg);
    
    // ... 省略代码                            
    return window;
}

2.1.1、vout_display_window_Attach实现分析:

// 【vlc/src/video_output/window.c】
// 关联widow模块和display模块层
/**
 * Attaches a window to a display. Window events will be dispatched to the
 * display until they are detached.
 */
void vout_display_window_Attach(vout_window_t *window, vout_display_t *vd)
{
    
    
    vout_display_window_t *state = window->owner.sys;

    // 设置window的大小
    // 此方法实现最终调用【window->control(window, query, ap);】,
    // 而由第十一章节中android native window加载分析可知,android设备中未实现该功能
    vout_window_SetSize(window,
                        vd->cfg->display.width, vd->cfg->display.height);

    vlc_mutex_lock(&state->lock);
    // window模块关联display模块
    state->vd = vd;

    // 发送视频展示大小事件给display模块层
    vout_display_SendEventDisplaySize(vd, state->width, state->height);
    vlc_mutex_unlock(&state->lock);
}

// 【vlc/src/video_output/window.c】
static inline void vout_display_SendEventDisplaySize(vout_display_t *vd, int width, int height)
{
    
    
    vout_display_SendEvent(vd, VOUT_DISPLAY_EVENT_DISPLAY_SIZE, width, height);
}

// [vlc/include/vlc_vout_display.c]
static inline void vout_display_SendEvent(vout_display_t *vd, int query, ...)
{
    
    
    va_list args;
    va_start(args, query);
    // 最终调用了display模块层的该调用连,而该event方法赋值见上面第1小节中
    // 【owner.event      = VoutDisplayEvent;】
    // 见小节下面分析 
    vd->owner.event(vd, query, args);
    va_end(args);
}

// VOUT_DISPLAY_EVENT_DISPLAY_SIZE事件分析为:
static void VoutDisplayEvent(vout_display_t *vd, int event, va_list args)
{
    
    
    vout_display_owner_sys_t *osys = vd->owner.sys;

    switch (event) {
    
    
    case VOUT_DISPLAY_EVENT_DISPLAY_SIZE: {
    
    
        const int width  = (int)va_arg(args, int);
        const int height = (int)va_arg(args, int);
        msg_Dbg(vd, "VoutDisplayEvent 'resize' %dx%d", width, height);

        /* */
        vlc_mutex_lock(&osys->lock);

        // 即改变了display展示模块的图像展示宽高值重置视图显示宽高
        osys->ch_display_size   = true;
        osys->display_width     = width;
        osys->display_height    = height;

        vlc_mutex_unlock(&osys->lock);
        break;
    }
    }   
}

2.2、AndroidWindow_New实现分析:

// 【vlc/modules/video_output/android/display.c】
static android_window *AndroidWindow_New(vout_display_t *vd,
                                         video_format_t *p_fmt,
                                         enum AWindow_ID id,
                                         bool b_use_priv)
{
    
    
    vout_display_sys_t *sys = vd->sys;
    android_window *p_window = NULL;

    p_window = calloc(1, sizeof(android_window));
    if (!p_window)
        goto error;

    // window类型,此次为video window类型
    p_window->id = id;
    // surfaceView展示方式时此值为false
    p_window->b_opaque = p_fmt->i_chroma == VLC_CODEC_ANDROID_OPAQUE;
    if (!p_window->b_opaque) {
    
    
        // 若true则表示:有使用android native window的私有API【system/core/include/system/window.h】
        p_window->b_use_priv = sys->b_has_anwp && b_use_priv;

        // 转换成android window支持的色彩模式
        p_window->i_android_hal = ChromaToAndroidHal(p_fmt->i_chroma);
        if (p_window->i_android_hal == -1)
            goto error;
    }

    // 根据视频图像方向角度设置window的角度
    switch (p_fmt->orientation)
    {
    
    
        case ORIENT_ROTATED_90:
            p_window->i_angle = 90;
            break;
        case ORIENT_ROTATED_180:
            p_window->i_angle = 180;
            break;
        case ORIENT_ROTATED_270:
            p_window->i_angle = 270;
            break;
        default:
            p_window->i_angle = 0;
    }
    // 若不使用window的私有API则转换一下方向角度类型
    if (p_window->b_use_priv)
        p_window->fmt = *p_fmt;
    else
        video_format_ApplyRotation(&p_window->fmt, p_fmt);
    p_window->i_pic_count = 1;

    // 连接/关联android native window/Surface对象
    // 见下面的分析
    if (AndroidWindow_ConnectSurface(sys, p_window) != 0)
    {
    
    
        if (id == AWindow_Video)
            msg_Err(vd, "can't get Video Surface");
        else if (id == AWindow_Subtitles)
            msg_Err(vd, "can't get Subtitles Surface");
        goto error;
    }

    return p_window;
error:
    free(p_window);
    return NULL;
}

// 【vlc/modules/video_output/android/display.c】
static int AndroidWindow_ConnectSurface(vout_display_sys_t *sys,
                                        android_window *p_window)
{
    
    
    if (!p_window->p_surface) {
    
    
        // 创建android native window/Surface对象
        // 【主要是从java层Surface中获取对应的native window对象】
        // 见下面的分析
        p_window->p_surface = AWindowHandler_getANativeWindow(sys->p_awh,
                                                              p_window->id);
        if (!p_window->p_surface)
            return -1;
        if (p_window->b_opaque)
            // 获取java层Surface对应的JNI层jobject类型的该对象引用
            p_window->p_jsurface = AWindowHandler_getSurface(sys->p_awh,
                                                             p_window->id);
    }

    return 0;
}

// 【vlc/modules/video_output/android/utils.c】
ANativeWindow *
AWindowHandler_getANativeWindow(AWindowHandler *p_awh, enum AWindow_ID id)
{
    
    
    assert(id < AWindow_Max);

    JNIEnv *p_env;

    if (p_awh->views[id].p_anw)
        return p_awh->views[id].p_anw;

    p_env = AWindowHandler_getEnv(p_awh);
    if (!p_env)
        return NULL;

    // 获取全局的对应java层Surface对象的JNI层对应引用
    // 见下面的分析
    if (WindowHandler_NewSurfaceEnv(p_awh, p_env, id) != VLC_SUCCESS)
        return NULL;
    assert(p_awh->views[id].jsurface != NULL);

    // 保存当前ID类型(window类型)的android native window对象【ANativeWindow】
    // 【p_awh->pf_winFromSurface】该方法的分析流程见第十一章中的第2小节分析
    p_awh->views[id].p_anw = p_awh->pf_winFromSurface(p_env,
                                                      p_awh->views[id].jsurface);
    return p_awh->views[id].p_anw;
}

// 【vlc/modules/video_output/android/utils.c】
static int
WindowHandler_NewSurfaceEnv(AWindowHandler *p_awh, JNIEnv *p_env,
                            enum AWindow_ID id)
{
    
    
    jobject jsurface;

    switch (id)
    {
    
    
        case AWindow_Video:
            // 该方法为宏定义调用java层对应名称的方法,由此可知:
            // 调用的是【jfields.AndroidNativeWindow.getVideoSurface】
            // 即java层的【AWindow.java】的【getVideoSurface】方法获取Surface对象
            jsurface = JNI_ANWCALL(CallObjectMethod, getVideoSurface);
            break;
        case AWindow_Subtitles:
            jsurface = JNI_ANWCALL(CallObjectMethod, getSubtitlesSurface);
            break;
        case AWindow_SurfaceTexture:
            jsurface = JNI_STEXCALL(CallObjectMethod, getSurface);
            break;
    }
    if (!jsurface)
        return VLC_EGENERIC;

    // 转换为新全局引用并保存
    p_awh->views[id].jsurface = (*p_env)->NewGlobalRef(p_env, jsurface);
    (*p_env)->DeleteLocalRef(p_env, jsurface);
    return VLC_SUCCESS;
}

2.3、AndroidWindow_Setup实现分析:【vlc/modules/video_output/android/display.c】

static int AndroidWindow_Setup(vout_display_sys_t *sys,
                               android_window *p_window,
                               unsigned int i_pic_count)
{
    
    
    bool b_java_configured = false;

    if (i_pic_count != 0)
        p_window->i_pic_count = i_pic_count;

    if (!p_window->b_opaque) {
    
    
        // 由前面分析可知Surface方式展示时,会进入此处
        
        // 像素对齐
        int align_pixels;
        // 创建图像对象,如下只是为了获取图像的宽高计算
        picture_t *p_pic = PictureAlloc(sys, &p_window->fmt, false);

        // For RGB (32 or 16) we need to align on 8 or 4 pixels, 16 pixels for YUV
        align_pixels = (16 / p_pic->p[0].i_pixel_pitch) - 1;
        p_window->fmt.i_height = p_pic->format.i_height;
        p_window->fmt.i_width = (p_pic->format.i_width + align_pixels) & ~align_pixels;
        picture_Release(p_pic);

        // 此处调用了java层【AWindow.java】的【setBuffersGeometry】方法,但该方法是空实现
        // 因此b_java_configured还是为false
        if (AndroidWindow_ConfigureJavaSurface(sys, p_window,
                                               &b_java_configured) != 0)
            return -1;

        // 若使用私有API,则执行私有API的window方法等,否则执行【AndroidWindow_SetupANW】方法
        if (!p_window->b_use_priv
            || AndroidWindow_SetupANWP(sys, p_window, b_java_configured) != 0) {
    
    
            if (AndroidWindow_SetupANW(sys, p_window, b_java_configured) != 0)
                return -1;
        }
    } else {
    
    
        // 其他情况则图像缓冲数默认31个
        sys->p_window->i_pic_count = 31; // TODO
        sys->p_window->i_min_undequeued = 0;
    }

    return 0;
}

3、Pool实现分析:

// 【vlc/modules/video_output/android/display.c】
static picture_pool_t *Pool(vout_display_t *vd, unsigned requested_count)
{
    
    
    vout_display_sys_t *sys = vd->sys;

    if (sys->pool == NULL)
        // 若当前display展示模块未创建图像缓存池buffer
        // 则根据请求的图像池大小【即帧数据个数】创建并分配内存
        sys->pool = PoolAlloc(vd, requested_count);
    return sys->pool;
}

// 【vlc/modules/video_output/android/display.c】
static picture_pool_t *PoolAlloc(vout_display_t *vd, unsigned requested_count)
{
    
    
    vout_display_sys_t *sys = vd->sys;
    picture_pool_t *pool = NULL;
    picture_t **pp_pics = NULL;
    unsigned int i = 0;

    msg_Dbg(vd, "PoolAlloc: request %d frames", requested_count);
    // 该方法分析在上面已分析
    if (AndroidWindow_Setup(sys, sys->p_window, requested_count) != 0)
        goto error;

    // 图像池大小【即帧数据个数】
    requested_count = sys->p_window->i_pic_count;
    msg_Dbg(vd, "PoolAlloc: got %d frames", requested_count);

    // 更新视频的尺寸大小,并通知java层视频窗口更新大小
    // 见下面的分析
    UpdateVideoSize(sys, &sys->p_window->fmt, sys->p_window->b_use_priv);

    // 分配图像缓冲池数组内存,【requested_count】为要被分配的元素个数,calloc会设置内存为0
    pp_pics = calloc(requested_count, sizeof(picture_t));

    // for循环为初始化图像缓冲池中每个图像对象picture_t的基本数据内存分配和初始化
    // 【当前分配的picture_t没有携带图像数据,只有图像的一些格式参数等】
    for (i = 0; i < requested_count; i++)
    {
    
    
        picture_t *p_pic = PictureAlloc(sys, &sys->p_window->fmt,
                                        sys->p_window->b_opaque);
        if (!p_pic)
            goto error;

        pp_pics[i] = p_pic;
    }

    // 图像缓冲池的配置信息
    picture_pool_configuration_t pool_cfg;
    memset(&pool_cfg, 0, sizeof(pool_cfg));
    pool_cfg.picture_count = requested_count;
    pool_cfg.picture       = pp_pics;
    if (sys->p_window->b_opaque)
    {
    
    // window【不透明】情况下赋值方法指针后续调用
        // 见下面的分析
        pool_cfg.lock      = PoolLockOpaquePicture;
        pool_cfg.unlock    = PoolUnlockOpaquePicture;
    }
    else
    {
    
    // 当前surface渲染的情况下赋值方法指针后续调用
        // 见下面的分析
        pool_cfg.lock      = PoolLockPicture;
        pool_cfg.unlock    = PoolUnlockPicture;
    }
    // 此处处理为:对图像缓冲池对象进行扩展处理即主要为数据总大小字节对齐【必须为2的倍数处理】
    pool = picture_pool_NewExtended(&pool_cfg);

error:
    if (!pool && pp_pics) {
    
    
        for (unsigned j = 0; j < i; j++)
            picture_Release(pp_pics[j]);
    }
    free(pp_pics);
    return pool;
}

// 【vlc/modules/video_output/android/display.c】
static int UpdateVideoSize(vout_display_sys_t *sys, video_format_t *p_fmt,
                           bool b_cropped)
{
    
    
    // 图像的宽高
    unsigned int i_width, i_height;
    // SAR即图像采样宽高比即num/den
    unsigned int i_sar_num = 1, i_sar_den = 1;
    video_format_t rot_fmt;

    // 调整图像格式的展示角度
    // 扩展:
    // 手机相机录出来的数据本身就是横着的,
    // 要作的处理是,将相机输出的图像数据(一般是YUV420SP或YUV420P)旋转90°之后
    // 再写入到编码器进行编码,输出的H264流就是角度正常的。
    // 关于YUV420旋转网上有很多代码。同时需要注意的时候,图像旋转90°后宽高会对调,
    // 在编解码的时候注意一下宽高的设定,否则会出现花屏。
    video_format_ApplyRotation(&rot_fmt, p_fmt);

    if (rot_fmt.i_sar_num != 0 && rot_fmt.i_sar_den != 0) {
    
    
        i_sar_num = rot_fmt.i_sar_num;
        i_sar_den = rot_fmt.i_sar_den;
    }
    // 是否需要裁剪窗口大小
    if (b_cropped) {
    
    
        // 裁剪窗口大小的设置
        i_width = rot_fmt.i_visible_width;
        i_height = rot_fmt.i_visible_height;
    } else {
    
    
        // 默认图像大小作为视频布局大小
        i_width = rot_fmt.i_width;
        i_height = rot_fmt.i_height;
    }

    // 见下面的分析
    AWindowHandler_setVideoLayout(sys->p_awh, i_width, i_height,
                                  rot_fmt.i_visible_width,
                                  rot_fmt.i_visible_height,
                                  i_sar_num, i_sar_den);
    return 0;
}

// 【vlc/modules/video_output/android/utils.c】
int
AWindowHandler_setVideoLayout(AWindowHandler *p_awh,
                              int i_width, int i_height,
                              int i_visible_width, int i_visible_height,
                              int i_sar_num, int i_sar_den)
{
    
    
    assert(p_awh->b_has_video_layout_listener);
    JNIEnv *p_env = AWindowHandler_getEnv(p_awh);
    if (!p_env)
        return VLC_EGENERIC;

    // 调用了java层AWindow对象的【setVideoLayout】方法,通知视频界面窗口大小变化
    JNI_ANWCALL(CallVoidMethod, setVideoLayout, i_width, i_height,
                i_visible_width,i_visible_height, i_sar_num, i_sar_den);
    return VLC_SUCCESS;
}

// 【vlc/modules/video_output/android/display.c】
static int PoolLockOpaquePicture(picture_t *p_pic)
{
    
    
    picture_sys_t *p_picsys = p_pic->p_sys;

    // 设置【不透明】window的该信息字段为true即可lock标识
    p_picsys->b_locked = true;
    return 0;
}

// 【vlc/modules/video_output/android/display.c】
static void PoolUnlockOpaquePicture(picture_t *p_pic)
{
    
    
    picture_sys_t *p_picsys = p_pic->p_sys;

    // 即释放当前图像内存
    AndroidOpaquePicture_Release(p_picsys, false);
}

// 【vlc/modules/video_output/android/display.h】
static inline void
AndroidOpaquePicture_Release(picture_sys_t *p_picsys, bool b_render)
{
    
    
    if (!p_picsys->b_locked)
        return;
    vlc_mutex_lock(&p_picsys->hw.lock);
    if (p_picsys->hw.i_index >= 0)
    {
    
    
        assert(p_picsys->hw.pf_release && p_picsys->hw.p_dec);
        // 功能:请求硬件处理模块释放图像内存。
        // 根据[pf_release]该方法的初始化代码搜索可知,
        // 该方法是android OpenMAX模块组件mediacodec加载时设置的。
        // 见后续OMX对应章节分析
        p_picsys->hw.pf_release(p_picsys->hw.p_dec,
                                (unsigned int) p_picsys->hw.i_index,
                                b_render);
        p_picsys->hw.i_index = -1;
    }
    vlc_mutex_unlock(&p_picsys->hw.lock);
    // 设置lock标识为false
    p_picsys->b_locked = false;
}

// 【vlc/modules/video_output/android/display.c】
static int PoolLockPicture(picture_t *p_pic)
{
    
    
    picture_sys_t *p_picsys = p_pic->p_sys;
    vout_display_sys_t *sys = p_picsys->sw.p_vd_sys;

    if (AndroidWindow_LockPicture(sys, sys->p_window, p_pic) != 0)
        return -1;

    return 0;
}
// 【vlc/modules/video_output/android/display.c】
static int AndroidWindow_LockPicture(vout_display_sys_t *sys,
                                     android_window *p_window,
                                     picture_t *p_pic)
{
    
    
    picture_sys_t *p_picsys = p_pic->p_sys;

    if (p_picsys->b_locked)
        return -1;

    if (p_window->b_use_priv) {
    
    
        void *p_handle;
        int err;

        // 调用native window私有API【lockData】方法请求lock当前图像数据
        err = sys->anwp.lockData(p_window->p_surface_priv,
                                 &p_handle, &p_picsys->sw.buf);
        if (err != 0)
            return -1;
        p_picsys->sw.p_handle = p_handle;
    } else {
    
    
        // 直接调用android native window的lock方法,该方法赋值和分析见第十一章节中的分析
        // [最终调用的android Surface的lock/lock2方法请求lock图像]
        if (sys->anw->winLock(p_window->p_surface,
                              &p_picsys->sw.buf, NULL) != 0)
            return -1;
    }
    if (p_picsys->sw.buf.width < 0 ||
        p_picsys->sw.buf.height < 0 ||
        (unsigned)p_picsys->sw.buf.width < p_window->fmt.i_width ||
        (unsigned)p_picsys->sw.buf.height < p_window->fmt.i_height)
    {
    
    // 图像尺寸大小有错误
        p_picsys->b_locked = true;
        // 见下面的分析
        AndroidWindow_UnlockPicture(sys, p_window, p_pic, false);
        return -1;
    }

    // 图像平面数据开始像素指针
    p_pic->p[0].p_pixels = p_picsys->sw.buf.bits;
    // 图像平面数据的行数即高度
    p_pic->p[0].i_lines = p_picsys->sw.buf.height;
    // 图像平面每行数据的字节数即通常说的步幅或步长【计算为:步长 = 图像的宽 * 每个像素点总 bit 数 / 8】
    // [i_pixel_pitch]该值默认为1
    p_pic->p[0].i_pitch = p_pic->p[0].i_pixel_pitch * p_picsys->sw.buf.stride;

    // 若图像格式即色彩空间【像素数据】编码格式为YV12则进行初始化图像数据
    if (p_picsys->sw.buf.format == PRIV_WINDOW_FORMAT_YV12)
        // 实现:步长值16像素位对齐,处理YUV平面模式数据、处理平面模式UV数据交替存储情况
        // 见下面分析
        SetupPictureYV12(p_pic, p_picsys->sw.buf.stride);

    // 该lock标识设为true
    p_picsys->b_locked = true;
    return 0;
}

// 【vlc/modules/video_output/android/display.c】
#define ALIGN_16_PIXELS( x ) ( ( ( x ) + 15 ) / 16 * 16 )
static void SetupPictureYV12(picture_t *p_picture, uint32_t i_in_stride)
{
    
    
    // 步长16字节数对齐
    /* according to document of android.graphics.ImageFormat.YV12 */
    int i_stride = ALIGN_16_PIXELS(i_in_stride);
    int i_c_stride = ALIGN_16_PIXELS(i_stride / 2);

    // 步长
    p_picture->p->i_pitch = i_stride;

    // 处理YUV数据
    // plane_t对象代表平面图形域的描述
    /* Fill chroma planes for planar YUV */
    for (int n = 1; n < p_picture->i_planes; n++)
    {
    
    
        const plane_t *o = &p_picture->p[n-1];
        plane_t *p = &p_picture->p[n];

        p->p_pixels = o->p_pixels + o->i_lines * o->i_pitch;
        p->i_pitch  = i_c_stride;
        p->i_lines  = p_picture->format.i_height / 2;
        /*
          Explicitly set the padding lines of the picture to black (127 for YUV)
          since they might be used by Android during rescaling.
        */
        int visible_lines = p_picture->format.i_visible_height / 2;
        if (visible_lines < p->i_lines)
            memset(&p->p_pixels[visible_lines * p->i_pitch], 127, (p->i_lines - visible_lines) * p->i_pitch);
    }

    // 是否平面模式中UV数据交替存储
    if (vlc_fourcc_AreUVPlanesSwapped(p_picture->format.i_chroma,
                                      VLC_CODEC_YV12)) {
    
    
        // 交换UV平面数据的开始指针,即交换了UV的数据
        uint8_t *p_tmp = p_picture->p[1].p_pixels;
        p_picture->p[1].p_pixels = p_picture->p[2].p_pixels;
        p_picture->p[2].p_pixels = p_tmp;
    }
}

// 【vlc/modules/video_output/android/display.c】
static void AndroidWindow_UnlockPicture(vout_display_sys_t *sys,
                                        android_window *p_window,
                                        picture_t *p_pic,
                                        bool b_render)
{
    
    
    picture_sys_t *p_picsys = p_pic->p_sys;

    if (!p_picsys->b_locked)
        return;

    if (p_window->b_use_priv) {
    
    
        void *p_handle = p_picsys->sw.p_handle;

        if (p_handle != NULL)
            // 调用native window私有API【unlockData】方法请求unlock当前图像数据
            sys->anwp.unlockData(p_window->p_surface_priv, p_handle, b_render);
    } else
        // 直接调用android native window的unlock方法,该方法赋值和分析见第十一章节中的分析
        // [最终调用的android Surface的unlockAndPost方法请求unlock图像]
        sys->anw->unlockAndPost(p_window->p_surface);

    // 该lock标识设为false
    p_picsys->b_locked = false;
}

4、Prepare实现分析:

// 【vlc/modules/video_output/android/display.c】
static void Prepare(vout_display_t *vd, picture_t *picture,
                    subpicture_t *subpicture)
{
    
    
    vout_display_sys_t *sys = vd->sys;
    VLC_UNUSED(picture);

    // 字幕子图像处理
    if (subpicture && sys->p_sub_window) {
    
    
        // 若当前图像数据无效则清除释放
        if (sys->b_sub_invalid) {
    
    
            sys->b_sub_invalid = false;
            if (sys->p_sub_pic) {
    
    
                picture_Release(sys->p_sub_pic);
                sys->p_sub_pic = NULL;
            }
            if (sys->p_spu_blend) {
    
    
                filter_DeleteBlend(sys->p_spu_blend);
                sys->p_spu_blend = NULL;
            }
            free(sys->p_sub_buffer_bounds);
            sys->p_sub_buffer_bounds = NULL;
        }

        // 若字幕图像数据为空则重新初始化其android window对象,见上面已分析流程
        if (!sys->p_sub_pic
         && AndroidWindow_Setup(sys, sys->p_sub_window, 1) == 0)
            sys->p_sub_pic = PictureAlloc(sys, &sys->p_sub_window->fmt, false);
        // 若未创建字幕图像混合滤镜【对两幅图像请求】则创建,
        // 该滤镜为vlc中加载的组件模块【模块名:"video blending"】
        if (!sys->p_spu_blend && sys->p_sub_pic)
            sys->p_spu_blend = filter_NewBlend(VLC_OBJECT(vd),
                                               &sys->p_sub_pic->format);

        if (sys->p_sub_pic && sys->p_spu_blend)
            // 标记有字幕子图像数据
            sys->b_has_subpictures = true;
    }
    /* As long as no subpicture was received, do not call
       SubpictureDisplay since JNI calls and clearing the subtitles
       surface are expensive operations. */
    if (sys->b_has_subpictures)
    {
    
    // 当有字幕图像数据时处理
        SubpicturePrepare(vd, subpicture);
        if (!subpicture)
        {
    
    
            /* The surface has been cleared and there is no new
               subpicture to upload, do not clear again until a new
               subpicture is received. */
            sys->b_has_subpictures = false;
        }
    }
    if (sys->p_window->b_opaque
     && AndroidOpaquePicture_CanReleaseAtTime(picture->p_sys))
    {
    
    // “不透明”图像可以释放指定时间点的图像数据则进入
        mtime_t now = mdate();
        if (picture->date > now)
        {
    
    
            if (picture->date - now <= INT64_C(1000000))
                // 若待显示图像PTS时间点比当前时间差小于1秒内则执行请求指定时间释放release操作
                AndroidOpaquePicture_ReleaseAtTime(picture->p_sys, picture->date);
            else /* The picture will be displayed from the Display callback */
                msg_Warn(vd, "picture way too early to release at time");
        }
    }
}

5、Display实现分析:【vlc/modules/video_output/android/display.c】

static void Display(vout_display_t *vd, picture_t *picture,
                    subpicture_t *subpicture)
{
    
    // 图像【或携带字幕子图像】尽快展示并尽快释放
    vout_display_sys_t *sys = vd->sys;

    if (sys->p_window->b_opaque)
        // 若window为“不透明”,则渲染并释放“不透明”图像 ==》并需要渲染显示图像
        AndroidOpaquePicture_Release(picture->p_sys, true);
    else
        // 见上面相关分析 ==》并需要渲染显示图像
        AndroidWindow_UnlockPicture(sys, sys->p_window, picture, true);

    // 释放图像
    picture_Release(picture);

    if (sys->p_sub_pic)
        // 见上面相关分析 ==》并需要渲染显示字幕子图像
        AndroidWindow_UnlockPicture(sys, sys->p_sub_window, sys->p_sub_pic,
                                    true);

    if (subpicture)
        subpicture_Delete(subpicture);

    // 标记已展示
    sys->b_displayed = true;
}

6、Control实现分析:

// 【vlc/modules/video_output/android/display.c】
static int Control(vout_display_t *vd, int query, va_list args)
{
    
    
    vout_display_sys_t *sys = vd->sys;

    switch (query) {
    
    
    // window裁剪或宽高比设置 
    case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
    case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
    {
    
    
        msg_Dbg(vd, "change source crop/aspect");

        if (query == VOUT_DISPLAY_CHANGE_SOURCE_CROP) {
    
    
            // 【裁剪window大小】从源视频格式中复制成目标window尺寸格式
            video_format_CopyCrop(&sys->p_window->fmt, &vd->source);
            // 注意:该方法只有【p_window->p_surface_priv】该变量为true时才起作用
            // 调用了【sys->anwp.setCrop】该方法
            AndroidWindow_UpdateCrop(sys, sys->p_window);
        } else
            // 宽高比设置时,只copy源格式中的SAR采样宽高比数据给window格式
            CopySourceAspect(&sys->p_window->fmt, &vd->source);

        // 更新视频window尺寸并通知java层窗口变化,
        // 见上面的相关分析
        UpdateVideoSize(sys, &sys->p_window->fmt, sys->p_window->b_use_priv);
        // 修正字幕子图像格式信息
        FixSubtitleFormat(sys);
        return VLC_SUCCESS;
    }
    case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
    {
    
    // 改变window显示大小请求
        const vout_display_cfg_t *cfg = va_arg(args, const vout_display_cfg_t *);

        // 获取并保存显示宽高改变值
        sys->i_display_width = cfg->display.width;
        sys->i_display_height = cfg->display.height;
        msg_Dbg(vd, "change display size: %dx%d", sys->i_display_width,
                                                  sys->i_display_height);
        // 同时修正字幕子图像格式信息
        FixSubtitleFormat(sys);
        return VLC_SUCCESS;
    }
    case VOUT_DISPLAY_RESET_PICTURES:
        // 当前Surface展示模式中不支持该请求控制
        vlc_assert_unreachable();
    default:
        msg_Warn(vd, "Unknown request in android-display: %d", query);
    // 当前Surface展示模式中不支持全屏和缩放窗口功能【电脑端支持】
    case VOUT_DISPLAY_CHANGE_ZOOM:
    case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
        return VLC_EGENERIC;
    }
}

以上该章节display组件模块展示层基本功能分析结束

猜你喜欢

转载自blog.csdn.net/u012430727/article/details/110940599