赞
踩
There are three primary threads spawned by libobs on initialization:
obs_graphics_thread for rendering video libobs/obs-video.c
video_thread for video encoding/output libobs/media-io/video-io.c
audio_thread for all audio processing/encoding/output libobs/media-io/audio-io.c
struct obs_core_video几个关键变量的注释:
- // 主画布 渲染当前场景下sources的画布
- // 渲染主窗口的窗口时 直接把这个纹理贴到display中
- gs_texture_t *render_texture;
-
- // output画布 如果直播录像时的宽高(output宽高)和主画布宽高不一样,
- // 需要在该画布上对render_texture做缩放
- gs_texture_t *output_texture;
-
- // 做GPU转换时 用来保存各个分量的画布 不可读
- // 做转换的纹理 就是render_texture或output_texture
- gs_texture_t *convert_textures[NUM_CHANNELS];
-
- // 保存供output使用的的YUV分量数组 可读
- // 这里的数据 经过map和read后 会保存在video_output::cache 等待video_thread读取
- gs_stagesurf_t *copy_surfaces[NUM_TEXTURES][NUM_CHANNELS];
=====================obs_graphics_thread====================
obs_graphics_thread中主要调三个函数:
- while(1)
- {
- profile_start(tick_sources_name);
- last_time = tick_sources(obs->video.video_time, last_time);
- profile_end(tick_sources_name);
-
- profile_start(output_frame_name);
- output_frame(raw_active, gpu_active);
- profile_end(output_frame_name);
-
- profile_start(render_displays_name);
- render_displays();
- profile_end(render_displays_name);
-
- Sleep(interval);
- }
tick_sources()中遍历了所有source,对每个source(不止是当前场景的source)调用obs_source_video_tick:
- pthread_mutex_lock(&data->sources_mutex);
- source = data->first_source;
- while (source)
- {
- struct obs_source *cur_source = obs_source_get_ref(source);
- source = (struct obs_source *)source->context.next;
- if (cur_source)
- {
- obs_source_video_tick(cur_source, seconds);
- obs_source_release(cur_source);
- }
- }
- pthread_mutex_unlock(&data->sources_mutex);
obs_source_video_tick中调用相关的XXX_tick函数,并且检查了UI存储的show_refs和activate_refs,如果存储的状态和source当前实际状态不一致,更新之(UI修改active状态后只更新了临时值,在videoRenderThread中才实际更新source):
scene_video_tick,obs_transition_tick,async_tick,source_info.video_tick
output_frame()中,做了如下事:
- static inline void output_frame(bool raw_active, const bool gpu_active)
- {
- struct obs_core_video *video = &obs->video;
-
- int cur_texture = video->cur_texture; // 本次渲染周期需要渲染的纹理索引
- int prev_texture = (cur_texture == 0) ? (NUM_TEXTURES - 1) : (cur_texture - 1); // 上次渲染的纹理索引
- bool frame_ready = 0;
-
- struct video_data frame;
- memset(&frame, 0, sizeof(struct video_data));
-
- profile_start(output_frame_gs_context_name);
- gs_enter_context(video->graphics);
- {
- profile_start(output_frame_render_video_name);
- GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_RENDER_VIDEO, output_frame_render_video_name);
- render_video(video, raw_active, gpu_active, cur_texture); // 渲染所有source 保存画布数据 更新下次渲染纹理的索引
- GS_DEBUG_MARKER_END();
- profile_end(output_frame_render_video_name);
-
- if (raw_active) // 当有推流或录像的时候 该值是true
- {
- profile_start(output_frame_download_frame_name);
- // 填充frame::data, frame::linesize
- frame_ready = download_frame(video, prev_texture, &frame);
- profile_end(output_frame_download_frame_name);
- }
-
- profile_start(output_frame_gs_flush_name);
- gs_flush(); // ID3D11DeviceContext::Flush()
- profile_end(output_frame_gs_flush_name);
- }
- gs_leave_context();
- profile_end(output_frame_gs_context_name);
-
- if (raw_active && frame_ready)
- {
- struct obs_vframe_info vframe_info;
- circlebuf_pop_front(&video->vframe_info_buffer, &vframe_info, sizeof(vframe_info));
-
- frame.timestamp = vframe_info.timestamp;
- profile_start(output_frame_output_video_data_name);
- // send video texture,触发信号量 通知video_thread线程可以取数据了
- output_video_data(video, &frame, vframe_info.count);
- profile_end(output_frame_output_video_data_name);
- }
-
- ++video->cur_texture;
- if (video->cur_texture == NUM_TEXTURES)
- video->cur_texture = 0;
- }
-
- static inline void render_video(struct obs_core_video *video, bool raw_active,
- const bool gpu_active, int cur_texture)
- {
- gs_begin_scene();
-
- gs_enable_depth_test(false);
- gs_set_cull_mode(GS_NEITHER);
-
- render_main_texture(video); // 渲染所有source到画布上
-
- if (raw_active || gpu_active) {
- // 将画布上的数据拷贝到纹理中, 此处格式是GS_RGBA
- // 画布对象是struct obs_core_video::render_texture
- // 如果画布宽高和output宽高一致,则返回的是obs_core_video::render_texture,
- // 否则在obs_core_video::output_texture对render_texture做缩放后 返回output_texture
- gs_texture_t *texture = render_output_texture(video);
-
- #ifdef _WIN32
- if (gpu_active)
- gs_flush();
- #endif
-
- if (video->gpu_conversion)// GPU格式转换
- render_convert_texture(video, texture);
-
- #ifdef _WIN32
- if (gpu_active) {
- gs_flush();
- output_gpu_encoders(video, raw_active);
- }
- #endif
-
- // 如果有录像或直播 将GPU转换后的纹理保存下来
- // GPU转换后的纹理 存储在struct obs_core_video::convert_textures
- // 数据保存在obs_core_video::copy_surfaces
- if (raw_active)
- stage_output_texture(video, cur_texture);
- }
-
- gs_set_render_target(NULL, NULL);
- gs_enable_blending(true);
-
- gs_end_scene();
- }
其中render_main_texture会调用obs_view_render,渲染所有channel:
- #define MAX_CHANNELS 64
- void obs_view_render(obs_view_t *view)
- {
- if (!view)
- return;
-
- pthread_mutex_lock(&view->channels_mutex);
- for (size_t i = 0; i < MAX_CHANNELS; i++)
- {
- struct obs_source *source = view->channels[i];
- if (source)
- {
- if (source->removed)
- {
- obs_source_release(source);
- view->channels[i] = NULL;
- }
- else
- {
- // 默认情况下 每次循环此处逻辑会进入三次
- // 一次是当前应用的transition source
- // 一次是OBS自动添加的was output audio (desktop audio)
- // 一次是OBS自动添加的was input audio(麦克风)
- // 但是后两个audio的channel 在调用obs_source_video_render时什么都不会做 直接return
- // 而transition source则会触发渲染当前scene 或做两个scene的转场
- obs_source_video_render(source);
- }
- }
- }
- pthread_mutex_unlock(&view->channels_mutex);
- }
遍历和渲染scene的每个source的函数是scene_video_render,代码如下:
- item = scene->first_item;
- while (item)
- {
- if (item->user_visible)
- render_item(item); // 会调到video_render
- item = item->next;
- }
static inline void render_item(struct obs_scene_item *item) 中设置每个source对应的矩阵matrix:
- gs_matrix_push(); // 保存之前的矩阵
- gs_matrix_mul(&item->draw_transform); // 设置sourceitem的矩阵
- if (item->item_render)
- {
- render_item_texture(item);
- }
- else
- {
- obs_source_video_render(item->source);
- }
- gs_matrix_pop(); // 恢复之前的矩阵
draw_transform是每个source的矩阵,每当其位置缩放旋转发生变化,都会调用函数更新矩阵:
static void update_item_transform(struct obs_scene_item *item, bool update_tex)
当UI上修改了缩放位置时 会直接调用这个函数
render_displays()中,遍历了所有obs_display,并对每个display调用其注册的所有回调函数
void render_display(struct obs_display *display),该函数中会调用display所有的draw_callbacks。
在draw_callbacks中会完成纹理的矩阵设置和渲染,典型的draw_callbacks如下:
OBSBasicProperties::DrawPreview (属性窗口)
OBSBasic::RenderProgram(studio mode下渲染右侧的已应用的scene)
OBSBasic::RenderMain (主窗口,如果是studio mode则是渲染左侧的编辑scene)关键函数如下:
- // 函数中又调用了obs_render_main_texture_internal
- // 之前已经把当前scene所有source画到obs_core_video::render_texture了
- // 直接将render_texture画到main窗口即可
- obs_render_main_texture_src_color_only();
-
- ===============================================
- if (window->IsPreviewProgramMode()) // studio mode
- {
- window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height));
-
- OBSScene scene = window->GetCurrentScene();
- obs_source_t *source = obs_scene_get_source(scene);
- if (source)
- {
- // 渲染编辑中的临时场景
- // 主画布的数据是渲染的已应用的scene的画面
- // 这个scene source是编辑中的临时场景 所以需要重新渲染其中的source
- obs_source_video_render(source);
- }
- }
- else
- {
- // 在主屏幕画当前scene的所有source : 直接将主画布的纹理贴到display即可
- obs_render_main_texture_src_color_only();
- }
-
- // 画main中选中source的边框和鼠标悬停source的边框
- window->ui->preview->DrawSceneEditing();
UI调用obs_display_create创建了display后,会调用obs_display_add_draw_callback注册渲染回调函数,libobs的videoRenderThread调用函数指针,就会到UI的回调,并由UI完成窗口渲染
=====================video_thread====================
该线程即使不直播不录像也会创建 只是一直阻塞住 等待信号量 直到output_video_data触发事件
每次触发后,会循环读完所有数据(video_output_cur_frame) 然后阻塞等待下一个信号触发,每处理一个frame 会增加total_frames。此处拿到的frame 已经是output设置的宽高和format
video_output_cur_frame会通过回调把数据发出去:
- pthread_mutex_lock(&video->input_mutex);
- for (size_t i = 0; i < video->inputs.num; i++)
- {
- struct video_input *input = video->inputs.array + i;
- struct video_data frame = frame_info->frame;
-
- if (scale_video_output(input, &frame))
- input->callback(input->param, &frame);
- }
- pthread_mutex_unlock(&video->input_mutex);
录像和直播,回调函数都是:static void receive_video(void *param, struct video_data *frame)
该函数中 会调用do_encode对video编码(音频也是调用该函数进行编码)如果编码获取到packet 会发送到interleave_packets av交错排序保证时间戳单调性:
- bool do_encode(struct obs_encoder *encoder, struct encoder_frame *frame)
- {
- profile_start(do_encode_name);
- if (!encoder->profile_encoder_encode_name)
- encoder->profile_encoder_encode_name =
- profile_store_name(obs_get_profiler_name_store(),
- "encode(%s)", encoder->context.name);
-
- struct encoder_packet pkt = {0};
- bool received = false;
- bool success;
-
- pkt.timebase_num = encoder->timebase_num;
- pkt.timebase_den = encoder->timebase_den;
- pkt.encoder = encoder;
-
- profile_start(encoder->profile_encoder_encode_name);
- success = encoder->info.encode(encoder->context.data, frame, &pkt,&received);
- profile_end(encoder->profile_encoder_encode_name);
- send_off_encoder_packet(encoder, success, received, &pkt); // interleave_packets
-
- profile_end(do_encode_name);
-
- return success;
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。