由此前分析过的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组件模块展示层基本功能分析结束