【八】【vlc-android】vlc-vout视频流输出端源码分析
1、由前面章节分析过可知vout结构体信息对象初始化是在decoder层的CreateDecoder方法中:
// [vlc/src/input/decoder.c]
// vout赋值初始化方法指针赋值
static decoder_t * CreateDecoder( vlc_object_t *p_parent,input_thread_t *p_input,const es_format_t *fmt,input_resource_t *p_resource,sout_instance_t *p_sout )
{// ... 省略部分代码decoder_t *p_dec;// 此处赋值了aout/vout的初始化方法指针/* Set buffers allocation callbacks for the decoders */p_dec->pf_aout_format_update = aout_update_format;p_dec->pf_vout_format_update = vout_update_format;// 该方法实现:获取vout端图像队列中的buffer// 【注:该图像队列池缓冲区其实最终是从display展示层模块获取的】// 此处暂不展开分析 TODOp_dec->pf_vout_buffer_new = vout_new_buffer;p_dec->pf_spu_buffer_new = spu_new_buffer;// ... 省略部分代码
}
// [vlc/src/input/decoder.c]
static int vout_update_format( decoder_t *p_dec )
{// ... 省略部分代码decoder_owner_sys_t *p_owner = p_dec->p_owner;vout_thread_t *p_vout;// 【dpb_size】查阅资料可知是指解码图像缓冲区(DPB)大小,以缓存帧数为单位unsigned dpb_size;switch( p_dec->fmt_in.i_codec ){case VLC_CODEC_HEVC:case VLC_CODEC_H264:case VLC_CODEC_DIRAC: /* FIXME valid ? */dpb_size = 18;break;case VLC_CODEC_VP5:case VLC_CODEC_VP6:case VLC_CODEC_VP6F:case VLC_CODEC_VP8:dpb_size = 3;break;default:dpb_size = 2;break;}p_vout = input_resource_RequestVout( p_owner->p_resource,p_vout, &fmt,dpb_size +p_dec->i_extra_picture_buffers + 1,true );vlc_mutex_lock( &p_owner->lock );p_owner->p_vout = p_vout;if( p_owner->p_input != NULL )// vout输出端创建完成后发送创建成功事件如通知java层等input_SendEventVout( p_owner->p_input );// ... 省略部分代码
}
// [vlc/src/input/resource.c]
vout_thread_t *input_resource_RequestVout( input_resource_t *p_resource,vout_thread_t *p_vout,const video_format_t *p_fmt, unsigned dpb_size,bool b_recycle )
{vlc_mutex_lock( &p_resource->lock );vout_thread_t *p_ret = RequestVout( p_resource, p_vout, p_fmt, dpb_size, b_recycle );vlc_mutex_unlock( &p_resource->lock );return p_ret;
}
// [vlc/src/input/resource.c]
static vout_thread_t *RequestVout( input_resource_t *p_resource,vout_thread_t *p_vout,const video_format_t *p_fmt, unsigned dpb_size,bool b_recycle )
{vlc_assert_locked( &p_resource->lock );if( !p_vout && !p_fmt ){if( p_resource->p_vout_free ){msg_Dbg( p_resource->p_vout_free, "destroying useless vout" );vout_CloseAndRelease( p_resource->p_vout_free );p_resource->p_vout_free = NULL;}return NULL;}if( p_fmt ){// 视频格式信息不为空时/* */if( !p_vout && p_resource->p_vout_free ){// 重新使用空闲的vout对象msg_Dbg( p_resource->p_parent, "trying to reuse free vout" );p_vout = p_resource->p_vout_free;p_resource->p_vout_free = NULL;}else if( p_vout ){assert( p_vout != p_resource->p_vout_free );vlc_mutex_lock( &p_resource->lock_hold );// 若创建之前不为空则从vout输出端列表中删除旧vout对象TAB_REMOVE( p_resource->i_vout, p_resource->pp_vout, p_vout );vlc_mutex_unlock( &p_resource->lock_hold );}/* */vout_configuration_t cfg = {.vout = p_vout,.input = VLC_OBJECT(p_resource->p_input),.change_fmt = true,.fmt = p_fmt,.dpb_size = dpb_size,};// 根据vout配置信息初始化vout输出端对象// 见1.1小节分析p_vout = vout_Request( p_resource->p_parent, &cfg );if( !p_vout )return NULL;// 从input输入对象信息中获取展示title信息并发送给vout输出端命令队列中处理DisplayVoutTitle( p_resource, p_vout );/* Send original viewpoint to the input in order to update other ESes */if( p_resource->p_input != NULL )input_Control( p_resource->p_input, INPUT_SET_INITIAL_VIEWPOINT,&p_fmt->pose );vlc_mutex_lock( &p_resource->lock_hold );// 添加到当前vout输出端列表中TAB_APPEND( p_resource->i_vout, p_resource->pp_vout, p_vout );vlc_mutex_unlock( &p_resource->lock_hold );return p_vout;}else{// 视频格式信息为空时assert( p_vout );vlc_mutex_lock( &p_resource->lock_hold );TAB_REMOVE( p_resource->i_vout, p_resource->pp_vout, p_vout );const int i_vout_active = p_resource->i_vout;vlc_mutex_unlock( &p_resource->lock_hold );if( p_resource->p_vout_free || i_vout_active > 0 || !b_recycle ){// 回收摧毁当前vout对象,因为已经存在一个已激活保存的free空闲对象了if( b_recycle )msg_Dbg( p_resource->p_parent, "destroying vout (already one saved or active)" );vout_CloseAndRelease( p_vout );}else{// 刷新当前vout中的各种数据,转换为一个空闲对象并保存msg_Dbg( p_resource->p_parent, "saving a free vout" );vout_Flush( p_vout, 1 );vout_FlushSubpictureChannel( p_vout, -1 );vout_configuration_t cfg = {.vout = p_vout,.input = NULL,.change_fmt = false,.fmt = NULL,.dpb_size = 0,};p_resource->p_vout_free = vout_Request( p_resource->p_parent, &cfg );}return NULL;}
}
1.1、vout_Request实现分析:
// 【vlc/src/video_output/video_output.c】
vout_thread_t *vout_Request(vlc_object_t *object,const vout_configuration_t *cfg)
{vout_thread_t *vout = cfg->vout;if (cfg->change_fmt && !cfg->fmt) {if (vout)vout_CloseAndRelease(vout);return NULL;}// 若传入的vout存在则尝试复用即重新根据视频格式重新更新vout对象/* If a vout is provided, try reusing it */if (vout) {if (vout->p->input != cfg->input) {if (vout->p->input)spu_Attach(vout->p->spu, vout->p->input, false);vout->p->input = cfg->input;if (vout->p->input)spu_Attach(vout->p->spu, vout->p->input, true);}if (cfg->change_fmt) {vout_control_cmd_t cmd;vout_control_cmd_Init(&cmd, VOUT_CONTROL_REINIT);cmd.u.cfg = cfg;vout_control_Push(&vout->p->control, &cmd);vout_control_WaitEmpty(&vout->p->control);vout_IntfReinit(vout);}if (!vout->p->dead) {msg_Dbg(object, "reusing provided vout");return vout;}vout_CloseAndRelease(vout);msg_Warn(object, "cannot reuse provided vout");}// 如复用失败则根据当前配置信息重新创建新的vout对象return VoutCreate(object, cfg);
}// [vlc/src/video_output/video_output.c]
static vout_thread_t *VoutCreate(vlc_object_t *object,const vout_configuration_t *cfg)
{video_format_t original;if (VoutValidateFormat(&original, cfg->fmt))return NULL;// 创建video out输出端初始化对象/* Allocate descriptor */vout_thread_t *vout = vlc_custom_create(object,sizeof(*vout) + sizeof(*vout->p),"video output");if (!vout) {video_format_Clean(&original);return NULL;}/* */vout->p = (vout_thread_sys_t*)&vout[1];vout->p->original = original;// 解码图像缓冲区(DPB)大小,以缓存帧数为单位vout->p->dpb_size = cfg->dpb_size;// 初始化vout控制指令列表数据vout_control_Init(&vout->p->control);// 添加【VOUT_CONTROL_INIT】vout控制初始化指令到指令列表中vout_control_PushVoid(&vout->p->control, VOUT_CONTROL_INIT);// 初始化vout输出端图像显示统计状态值【原子性】即是否已成功显示和是否已丢失// 该原子性值此前分析是 decoder层用于判断当前图像在vout输出端的输出结果vout_statistic_Init(&vout->p->statistic);// 初始化快照图像信息结构体对象vout_snapshot_Init(&vout->p->snapshot);/* Initialize locks */vlc_mutex_init(&vout->p->filter.lock);vlc_mutex_init(&vout->p->spu_lock);// 初始化赋值一些“变量字段”和控制接口,// 功能选择的回调接口如变速【倍数】播放、【剪切】显示尺寸大小设置/* Take care of some "interface/control" related initialisations */vout_IntfInit(vout);// 初始化子图像单元信息对象,其实是对应的字幕显示信息/* Initialize subpicture unit */vout->p->spu = spu_Create(vout, vout);// title显示的配置vout->p->title.show = var_InheritBool(vout, "video-title-show");vout->p->title.timeout = var_InheritInteger(vout, "video-title-timeout");vout->p->title.position = var_InheritInteger(vout, "video-title-position");/* Get splitter name if present */vout->p->splitter_name = var_InheritString(vout, "video-splitter");// 隔行扫描信息处理/* */vout_InitInterlacingSupport(vout, vout->p->displayed.is_interlaced);/* Window */if (vout->p->splitter_name == NULL) {vout_window_cfg_t wcfg = {.is_standalone = !var_InheritBool(vout, "embedded-video"),.is_fullscreen = var_GetBool(vout, "fullscreen"),.type = VOUT_WINDOW_TYPE_INVALID,// TODO: take pixel A/R, crop and zoom into account
#ifdef __APPLE__.x = var_InheritInteger(vout, "video-x"),.y = var_InheritInteger(vout, "video-y"),
#endif.width = cfg->fmt->i_visible_width,.height = cfg->fmt->i_visible_height,};vout_window_t *window = vout_display_window_New(vout, &wcfg);if (window != NULL){if (var_InheritBool(vout, "video-wallpaper"))vout_window_SetState(window, VOUT_WINDOW_STATE_BELOW);else if (var_InheritBool(vout, "video-on-top"))vout_window_SetState(window, VOUT_WINDOW_STATE_ABOVE);}vout->p->window = window;} elsevout->p->window = NULL;/* */vlc_object_set_destructor(vout, VoutDestructor);// 重点:开启vout视频输出端工作线程,见第2小节分析// 主要处理视频图像buffer接收和图像显示控制事件/* */if (vlc_clone(&vout->p->thread, Thread, vout,VLC_THREAD_PRIORITY_OUTPUT)) {if (vout->p->window != NULL)vout_display_window_Delete(vout->p->window);spu_Destroy(vout->p->spu);vlc_object_release(vout);return NULL;}// 工作线程执行正常则进行判断条件成立进入等待// 【条件[while ((ctrl->cmd.i_size > 0 || ctrl->is_processing) && !ctrl->is_dead)]// 即当前命令列表中有命令或有命令正在执行中或当前指令列表未关闭则wait】// [vlc_cond_wait(&ctrl->wait_acknowledge, &ctrl->lock)]vout_control_WaitEmpty(&vout->p->control);if (vout->p->dead) {msg_Err(vout, "video output creation failed");vout_CloseAndRelease(vout);return NULL;}// 保存input输入端信息对象可进行交互vout->p->input = cfg->input;if (vout->p->input)spu_Attach(vout->p->spu, vout->p->input, true);return vout;
}
2、vout端工作线程Thead实现分析: [vlc/src/video_output/video_output.c]
/****************************************************************************** Thread: video output thread****************************************************************************** Video output thread. This function does only returns when the thread is* terminated. It handles the pictures arriving in the video heap and the* display device events.*****************************************************************************/
static void *Thread(void *object)
{vout_thread_t *vout = object;vout_thread_sys_t *sys = vout->p;mtime_t deadline = VLC_TS_INVALID;bool wait = false;for (;;) {vout_control_cmd_t cmd;if (wait){// 默认最大100毫秒的延迟等待图像数据的延迟等待时间const mtime_t max_deadline = mdate() + 100000;// 若此前已有延迟时间,那么则获取最小的那个时间点deadline = deadline <= VLC_TS_INVALID ? max_deadline : __MIN(deadline, max_deadline);} else {deadline = VLC_TS_INVALID;}// 图像输出控制指令出栈,然后执行相关指令任务// 注:由前面decoder层分析流程可知,有部分控制指令入栈操作是在decoder层传入// 见2.1小节分析while (!vout_control_Pop(&sys->control, &cmd, deadline))// 执行相关指令任务// 见2.2小节分析if (ThreadControl(vout, cmd))return NULL;// 若当前无指令任务,或所有任务都执行完毕且成功,则进入此处deadline = VLC_TS_INVALID;// 见2.3小节分析wait = ThreadDisplayPicture(vout, &deadline) != VLC_SUCCESS;// 是否是隔行扫描显示const bool picture_interlaced = sys->displayed.is_interlaced;// 判断扫描方式是否发生变化并进行更新,若发生变化则发送对应变量变化事件回调// 并等待30秒退出当前模式,并重新设置新模式vout_SetInterlacingState(vout, picture_interlaced);// 见2.4小节分析vout_ManageWrapper(vout);}
}
2.1、vout_control_Pop实现分析:
// [vlc/src/video_output/control.c]
int vout_control_Pop(vout_control_t *ctrl, vout_control_cmd_t *cmd,mtime_t deadline)
{vlc_mutex_lock(&ctrl->lock);if (ctrl->cmd.i_size <= 0) {// 当前没有可执行的指令任务,设置没有正在执行中状态ctrl->is_processing = false;// 唤醒当前可能正在等待指令入栈操作的线程vlc_cond_broadcast(&ctrl->wait_acknowledge);/* Spurious wakeups are perfectly fine */if (deadline > VLC_TS_INVALID && ctrl->can_sleep)// 由于当前没有指令任务可执行,而deadline时间还未到期且设置了允许当前vout线程sleep,// 则执行wait,等待指令输入端进行唤醒或等待指定时长【最多100毫秒】后自我唤醒vlc_cond_timedwait(&ctrl->wait_request, &ctrl->lock, deadline);}bool has_cmd;if (ctrl->cmd.i_size > 0) {has_cmd = true;// 有指令任务存在,则执行指令列表中第一个指令任务即FIFO队列,// 获取指令后移除当前指令*cmd = ARRAY_VAL(ctrl->cmd, 0);ARRAY_REMOVE(ctrl->cmd, 0);// 标记有指令正在执行中ctrl->is_processing = true;} else {has_cmd = false;// 没有指令则标记下次循环时若无指令任务则允许可sleep当前vout线程ctrl->can_sleep = true;}vlc_mutex_unlock(&ctrl->lock);return has_cmd ? VLC_SUCCESS : VLC_EGENERIC;
}
2.2、ThreadControl实现分析:
// [vlc/src/video_output/video_output.c]
static int ThreadControl(vout_thread_t *vout, vout_control_cmd_t cmd)
{switch(cmd.type) {case VOUT_CONTROL_INIT:// 由前面分析可知,该指令任务在第1小节中vout初始化过程中有入栈过。// 初始化vout一些状态:线程是否关闭、是否允许丢弃延迟帧、暂停时间、图像渲染时间估计信息ThreadInit(vout);// 启动工作线程的一些初始化设置// 见该小节后续分析if (ThreadStart(vout, NULL)){ThreadClean(vout);return 1;}break;case VOUT_CONTROL_CLEAN:ThreadStop(vout, NULL);ThreadClean(vout);return 1;case VOUT_CONTROL_REINIT:if (ThreadReinit(vout, cmd.u.cfg))return 1;break;// ... 省略部分代码}vout_control_cmd_Clean(&cmd);return 0;
}// [vlc/src/video_output/video_output.c]
static int ThreadStart(vout_thread_t *vout, vout_display_state_t *state)
{vlc_mouse_Init(&vout->p->mouse);// 重点:初始化用于decoder层decode之后获取一个新图像FIFO队列,// 然后入栈decoded已解码图像到该队列中vout->p->decoder_fifo = picture_fifo_New();vout->p->decoder_pool = NULL;vout->p->display_pool = NULL;vout->p->private_pool = NULL;vout->p->filter.configuration = NULL;// 将解码器端的视频格式赋值给过滤器【滤镜】格式video_format_Copy(&vout->p->filter.format, &vout->p->original);filter_owner_t owner = {.sys = vout,.video = {.buffer_new = VoutVideoFilterStaticNewPicture,},};// 初始化static视频滤镜链【过滤器链】filters,(过滤/转换)vout->p->filter.chain_static =filter_chain_NewVideo( vout, true, &owner );// 重新赋值新图像对象buffer数据的方法指针,上一个赋值的值是交给了【chain_static】对象中owner.video.buffer_new = VoutVideoFilterInteractiveNewPicture;// 然后再初始化filters交互过滤器链vout->p->filter.chain_interactive =filter_chain_NewVideo( vout, true, &owner );// 图像显示配置信息对象:显示的宽高、是否可自动缩放【is_display_filled】、显示位置等设置信息vout_display_state_t state_default;if (!state) {VoutGetDisplayCfg(vout, &state_default.cfg, vout->p->display.title);#if defined(_WIN32) || defined(__OS2__)bool below = var_InheritBool(vout, "video-wallpaper");bool above = var_InheritBool(vout, "video-on-top");state_default.wm_state = below ? VOUT_WINDOW_STATE_BELOW: above ? VOUT_WINDOW_STATE_ABOVE: VOUT_WINDOW_STATE_NORMAL;
#endifstate_default.sar.num = 0;state_default.sar.den = 0;state = &state_default;}// 初始化加载视频图像显示模块或带上视频剪辑/分割模块// 见display视频显示模块分析章节 TODOif (vout_OpenWrapper(vout, vout->p->splitter_name, state))goto error;// 主要初始化vout中多个图像buffer缓冲池对象,其中display和decoder层的pool均从display层获取// 见display视频显示模块分析章节 TODO// 注:【sys->display.use_dr】dr的大概意思是directly rendering直接渲染显示即不使用视频滤镜功能if (vout_InitWrapper(vout)){vout_CloseWrapper(vout, state);goto error;}assert(vout->p->decoder_pool && vout->p->private_pool);vout->p->displayed.current = NULL;vout->p->displayed.next = NULL;vout->p->displayed.decoded = NULL;vout->p->displayed.date = VLC_TS_INVALID;vout->p->displayed.timestamp = VLC_TS_INVALID;vout->p->displayed.is_interlaced = false;vout->p->step.last = VLC_TS_INVALID;vout->p->step.timestamp = VLC_TS_INVALID;vout->p->spu_blend_chroma = 0;vout->p->spu_blend = NULL;video_format_Print(VLC_OBJECT(vout), "original format", &vout->p->original);return VLC_SUCCESS;
error:// ...省略部分代码return VLC_EGENERIC;
}
2.3、ThreadDisplayPicture实现分析:
// [vlc/src/video_output/video_output.c]
static int ThreadDisplayPicture(vout_thread_t *vout, mtime_t *deadline)
{bool frame_by_frame = !deadline;bool paused = vout->p->pause.is_on;bool first = !vout->p->displayed.current;if (first)// 若当前为第一个图像展示【即此前没有展示的图像】,则获取当前应该展示的图像(可能从decoder的FIFO图像队列中获取)// 内部处理:会判断当前图像是否大于最大的延迟阈值(默认为20毫秒)// 见2.3.1小节分析if (ThreadDisplayPreparePicture(vout, true, frame_by_frame)) /* FIXME not sure it is ok */return VLC_EGENERIC;if (!paused || frame_by_frame)while (!vout->p->displayed.next && !ThreadDisplayPreparePicture(vout, false, frame_by_frame));const mtime_t date = mdate();// 下一个图像渲染延迟显示的间隔时间估值:图像预估渲染时间+4毫秒const mtime_t render_delay = vout_chrono_GetHigh(&vout->p->render) + VOUT_MWAIT_TOLERANCE;// 是否丢弃下一帧数据的处理bool drop_next_frame = frame_by_frame;mtime_t date_next = VLC_TS_INVALID;if (!paused && vout->p->displayed.next) {// 预估下一个图像显示时间date_next = vout->p->displayed.next->date - render_delay;if (date_next /* + 0 FIXME */ <= date)// 若下一个图像显示时间小于等于当前时间则标记为需要丢弃下一个图像数据drop_next_frame = true;}/* FIXME/XXX we must redisplay the last decoded picture (because* of potential vout updated, or filters update or SPU update)* For now a high update period is needed but it could be removed* if and only if:* - vout module emits events from theselves.* - *and* SPU is modified to emit an event or a deadline when needed.** So it will be done later.*/bool refresh = false;mtime_t date_refresh = VLC_TS_INVALID;if (vout->p->displayed.date > VLC_TS_INVALID) {// 图像展示刷新时间:当前PTS图像展示时间 + 80毫秒【默认两图像之间最大间隔时长】 - 【减去渲染延迟间隔时间估值】date_refresh = vout->p->displayed.date + VOUT_REDISPLAY_DELAY - render_delay;// 若当前时间图像刷新时间小于等于当前时间,则表示当前待显示图像需要进行显示refresh = date_refresh <= date;}// 是否强制刷新视频bool force_refresh = !drop_next_frame && refresh;if (!frame_by_frame) {// 若此前有线程执行最后更新时间,则此处更新该值if (date_refresh != VLC_TS_INVALID)*deadline = date_refresh;if (date_next != VLC_TS_INVALID && date_next < *deadline)*deadline = date_next;}if (!first && !refresh && !drop_next_frame) {return VLC_EGENERIC;}if (drop_next_frame) {// 丢弃下一个图像数据picture_Release(vout->p->displayed.current);vout->p->displayed.current = vout->p->displayed.next;vout->p->displayed.next = NULL;}if (!vout->p->displayed.current)return VLC_EGENERIC;// 立即显示当前图像/* display the picture immediately */bool is_forced = frame_by_frame || force_refresh || vout->p->displayed.current->b_force;// 图像展示渲染请求:主要有滤镜处理、字幕处理、图像裁切、最后传输到显示模块进行显示处理// 见display显示模块章节分析 TODOint ret = ThreadDisplayRenderPicture(vout, is_forced);return force_refresh ? VLC_EGENERIC : ret;
}
2.3.1、ThreadDisplayPreparePicture实现分析:
// [vlc/src/video_output/video_output.c]
static int ThreadDisplayPreparePicture(vout_thread_t *vout, bool reuse, bool frame_by_frame)
{// 是否应该丢弃延迟图像bool is_late_dropped = vout->p->is_late_dropped && !vout->p->pause.is_on && !frame_by_frame;vlc_mutex_lock(&vout->p->filter.lock);// 尝试从视频滤镜器链中获取图像数据picture_t *picture = filter_chain_VideoFilter(vout->p->filter.chain_static, NULL);assert(!reuse || !picture);while (!picture) {picture_t *decoded;if (reuse && vout->p->displayed.decoded) {// 重用当前保存的decoded已解码数据decoded = picture_Hold(vout->p->displayed.decoded);} else {// 若为空,则从图像FIFO队列中获取第一个数据decoded = picture_fifo_Pop(vout->p->decoder_fifo);if (decoded) {// 若获取到有图像数据if (is_late_dropped && !decoded->b_force) {// 若允许丢弃延迟图像并且不是强制显示则进入此处// 允许延迟显示时间阈值mtime_t late_threshold;if (decoded->format.i_frame_rate && decoded->format.i_frame_rate_base)// 通过帧率来计算合适的延迟显示时间阈值late_threshold = ((CLOCK_FREQ/2) * decoded->format.i_frame_rate_base) / decoded->format.i_frame_rate;else// 默认20毫秒延迟阈值late_threshold = VOUT_DISPLAY_LATE_THRESHOLD;// 预测显示时间值【当前时间值】const mtime_t predicted = mdate() + 0; /* TODO improve */// 计算当前图像延迟时间:预测显示时间减去当前图像PTS显示时间const mtime_t late = predicted - decoded->date;if (late > late_threshold) {// 若延迟时间大于允许的延迟阈值则丢弃该图像,进行下一个图像数据的获取msg_Warn(vout, "picture is too late to be displayed (missing %"PRId64" ms)", late/1000);picture_Release(decoded);vout_statistic_AddLost(&vout->p->statistic, 1);continue;} else if (late > 0) {// 延迟时长msg_Dbg(vout, "picture might be displayed late (missing %"PRId64" ms)", late/1000);}}if (!VideoFormatIsCropArEqual(&decoded->format, &vout->p->filter.format))// 若当前图像格式和过滤器格式不同如宽高比等属性,则进行重置过滤器信息如更新过滤器相关的图像格式信息等ThreadChangeFilters(vout, &decoded->format, vout->p->filter.configuration, -1, true);}}if (!decoded)break;reuse = false;if (vout->p->displayed.decoded)picture_Release(vout->p->displayed.decoded);// 赋值显示相关状态值vout->p->displayed.decoded = picture_Hold(decoded);vout->p->displayed.timestamp = decoded->date;vout->p->displayed.is_interlaced = !decoded->b_progressive;// 从第一个视频滤镜器链中滤镜当前图像数据得到最终图像结果picture = filter_chain_VideoFilter(vout->p->filter.chain_static, decoded);}vlc_mutex_unlock(&vout->p->filter.lock);if (!picture)return VLC_EGENERIC;assert(!vout->p->displayed.next);if (!vout->p->displayed.current)vout->p->displayed.current = picture;elsevout->p->displayed.next = picture;return VLC_SUCCESS;
}
2.4、vout_ManageWrapper实现分析:
// [vlc/src/video_output/vout_wrapper.c]
void vout_ManageWrapper(vout_thread_t *vout)
{vout_thread_sys_t *sys = vout->p;vout_display_t *vd = sys->display.vd;// true为当前display图像展示模块标记了当前图像池buffer数据无效bool reset_display_pool = vout_AreDisplayPicturesInvalid(vd);// 管理display图像显示模块的一些配置reset_display_pool |= vout_ManageDisplay(vd, !sys->display.use_dr || reset_display_pool);if (reset_display_pool) {// true为展示模块有过滤器链的使用sys->display.use_dr = !vout_IsDisplayFiltered(vd);NoDrInit(vout);}
}
// [vlc/src/video_output/vout_wrapper.c]
static void NoDrInit(vout_thread_t *vout)
{vout_thread_sys_t *sys = vout->p;// 若为true则从display层获取展示图像池buffer缓冲区对象,否则初始化为空if (sys->display.use_dr)sys->display_pool = vout_display_Pool(sys->display.vd, 3);elsesys->display_pool = NULL;
}
3、由上面节分析过可知vout结构体信息对象初始化方法为【vout_update_format】,而该方法通过调用分析可知,在如下方法中调用:
// 【vlc/include/vlc_codec.c】
VLC_USED
static inline int decoder_UpdateVideoFormat( decoder_t *dec )
{assert( dec->fmt_in.i_cat == VIDEO_ES );if( dec->fmt_in.i_cat == VIDEO_ES && dec->pf_vout_format_update != NULL )return dec->pf_vout_format_update( dec );elsereturn -1;
}// 而该方法被调用地方主要在:
// 【lavc_UpdateVideoFormat】和【DecodeSidedata】方法中,
// 【主要】均在decoder层的解码流程中解码方法【DecodeBlock】中被调用的。
由此此前第五六章分析中涉及vout输出端的TODO部分均已基本分析完成了。
【八】【vlc-android】vlc-vout视频流输出端源码分析相关推荐
- 【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )
Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...
- 【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 )
Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...
- Android shortcut的使用及源码分析
Android shortcut的使用及源码分析 最近遇到了一个切换国家码后部分应用的shortcut未更新的问题,就学习了shortcut的相关知识,在这里分享一下我了解的知识,希望能对大家有帮助. ...
- Android Q 10.1 KeyMaster源码分析(二) - 各家方案的实现
写在之前 这两篇文章是我2021年3月初看KeyMaster的笔记,本来打算等分析完KeyMaster和KeyStore以后再一起做成一系列贴出来,后来KeyStore的分析中断了,这一系列的文章就变 ...
- 【Android 插件化】VirtualApp 源码分析 ( 启动应用源码分析 | HomePresenterImpl 启动应用方法 | VirtualCore 启动插件应用最终方法 )
文章目录 一.启动应用源码分析 1.HomeActivity 启动应用点击方法 2.HomePresenterImpl 启动应用方法 3.VirtualCore 启动插件应用最终方法 一.启动应用源码 ...
- 【Android 插件化】VirtualApp 源码分析 ( 添加应用源码分析 | LaunchpadAdapter 适配器 | 适配器添加元素 | PackageAppData 元素 )
文章目录 一.添加应用源码分析 1.LaunchpadAdapter 适配器 2.适配器添加元素 3.PackageAppData 元素 一.添加应用源码分析 1.LaunchpadAdapter 适 ...
- 【Android 安全】DEX 加密 ( Application 替换 | Android 应用启动原理 | Instrumentation 源码分析 )
文章目录 一.Instrumentation 源码分析 二.Instrumentation 创建 Application 相关的部分源码 dex 解密时 , 需要将 代理 Application 替换 ...
- 【Android 安全】DEX 加密 ( Application 替换 | Android 应用启动原理 | LoadedApk 源码分析 )
文章目录 一.LoadedApk 源码分析 二.LoadedApk 源码 makeApplication 方法分析 dex 解密时 , 需要将 代理 Application 替换为 真实 Applic ...
- 【Android 安全】DEX 加密 ( Application 替换 | Android 应用启动原理 | ActivityThread 源码分析 )
文章目录 一.ActivityThread 源码分析 二.ActivityThread 部分代码示例 dex 解密时 , 需要将 代理 Application 替换为 真实 Application ; ...
最新文章
- AIoT的发展路上,英特尔如何通过边缘计算掀起产业变革
- 新发现为类脑计算机开辟了道路
- [转载]:TRY...CATCH (Transact-SQL)
- django学习(2)----APP
- dgi数据治理_荐书 | 5G时代组织急需数据体检
- 怎样用JS来添加CSS样式
- 「兼容M1」iZotope RX 9 Advanced for Mac - 音频修复工具
- python-16: time 模块 之一
- 2009年上半年软考所有试题和答案公布 专家解析中
- c语言网格搜索,使用逻辑回归时怎么利用网格搜索来查找degree,c等超参数
- 高校学籍管理系统(SQL Server数据库课程设计)
- 多边形扩展和收缩(凸多边形和凹多边形)
- php 微信支付md5签名,微信支付V3支付签名无效解决方案
- 协调才暴力-精英乒乓论坛
- rpm搭建LAMP+Discuz论坛
- 教你几招!做客服怎么应对物流太慢的问题
- 【华人学者风采】周昆 浙江大学
- Android ROM中加入第三方APP
- phpstorm 配置 xdebug断点调试
- intent.setComponent()方法
热门文章
- pikachu全网最详细安装教程
- 【智慧农业】LORA农业灌溉解决方案
- Google Chrome 75.0.3770.100 插件丰富且自带翻译的浏览器
- 蝴蝶键盘 Linux,Macbook蝴蝶键盘与普通键盘有什么不一样? 蝶式结构键盘解析
- 【万字干货】产业互联网B端产品经理实操手册
- dsf5.0组件相关
- yum -- Failed connect to mirrors.aliyuncs.com:80; No route to host
- 北航计算机学院编译,北航计算机学院编译习题讲解.pdf
- python中常用英语口语_1000句常用英语口语
- Java 视频转换h265、h264、mkv、mp4