当前位置:   article > 正文

[OBS] 分析libobs几个主要的线程_gs_debug_marker_begin

gs_debug_marker_begin

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几个关键变量的注释:

  1. // 主画布 渲染当前场景下sources的画布
  2. // 渲染主窗口的窗口时 直接把这个纹理贴到display中
  3. gs_texture_t *render_texture;
  4. // output画布 如果直播录像时的宽高(output宽高)和主画布宽高不一样,
  5. // 需要在该画布上对render_texture做缩放
  6. gs_texture_t *output_texture;
  7. // 做GPU转换时 用来保存各个分量的画布 不可读
  8. // 做转换的纹理 就是render_texture或output_texture
  9. gs_texture_t *convert_textures[NUM_CHANNELS];
  10. // 保存供output使用的的YUV分量数组 可读
  11. // 这里的数据 经过map和read后 会保存在video_output::cache 等待video_thread读取
  12. gs_stagesurf_t *copy_surfaces[NUM_TEXTURES][NUM_CHANNELS];

=====================obs_graphics_thread====================

obs_graphics_thread中主要调三个函数:

  1. while(1)
  2. {
  3. profile_start(tick_sources_name);
  4. last_time = tick_sources(obs->video.video_time, last_time);
  5. profile_end(tick_sources_name);
  6. profile_start(output_frame_name);
  7. output_frame(raw_active, gpu_active);
  8. profile_end(output_frame_name);
  9. profile_start(render_displays_name);
  10. render_displays();
  11. profile_end(render_displays_name);
  12. Sleep(interval);
  13. }

tick_sources()中遍历了所有source,对每个source(不止是当前场景的source)调用obs_source_video_tick:

  1. pthread_mutex_lock(&data->sources_mutex);
  2. source = data->first_source;
  3. while (source)
  4. {
  5. struct obs_source *cur_source = obs_source_get_ref(source);
  6. source = (struct obs_source *)source->context.next;
  7. if (cur_source)
  8. {
  9. obs_source_video_tick(cur_source, seconds);
  10. obs_source_release(cur_source);
  11. }
  12. }
  13. 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()中,做了如下事:

  1. static inline void output_frame(bool raw_active, const bool gpu_active)
  2. {
  3. struct obs_core_video *video = &obs->video;
  4. int cur_texture = video->cur_texture; // 本次渲染周期需要渲染的纹理索引
  5. int prev_texture = (cur_texture == 0) ? (NUM_TEXTURES - 1) : (cur_texture - 1); // 上次渲染的纹理索引
  6. bool frame_ready = 0;
  7. struct video_data frame;
  8. memset(&frame, 0, sizeof(struct video_data));
  9. profile_start(output_frame_gs_context_name);
  10. gs_enter_context(video->graphics);
  11. {
  12. profile_start(output_frame_render_video_name);
  13. GS_DEBUG_MARKER_BEGIN(GS_DEBUG_COLOR_RENDER_VIDEO, output_frame_render_video_name);
  14. render_video(video, raw_active, gpu_active, cur_texture); // 渲染所有source 保存画布数据 更新下次渲染纹理的索引
  15. GS_DEBUG_MARKER_END();
  16. profile_end(output_frame_render_video_name);
  17. if (raw_active) // 当有推流或录像的时候 该值是true
  18. {
  19. profile_start(output_frame_download_frame_name);
  20. // 填充frame::data, frame::linesize
  21. frame_ready = download_frame(video, prev_texture, &frame);
  22. profile_end(output_frame_download_frame_name);
  23. }
  24. profile_start(output_frame_gs_flush_name);
  25. gs_flush(); // ID3D11DeviceContext::Flush()
  26. profile_end(output_frame_gs_flush_name);
  27. }
  28. gs_leave_context();
  29. profile_end(output_frame_gs_context_name);
  30. if (raw_active && frame_ready)
  31. {
  32. struct obs_vframe_info vframe_info;
  33. circlebuf_pop_front(&video->vframe_info_buffer, &vframe_info, sizeof(vframe_info));
  34. frame.timestamp = vframe_info.timestamp;
  35. profile_start(output_frame_output_video_data_name);
  36. // send video texture,触发信号量 通知video_thread线程可以取数据了
  37. output_video_data(video, &frame, vframe_info.count);
  38. profile_end(output_frame_output_video_data_name);
  39. }
  40. ++video->cur_texture;
  41. if (video->cur_texture == NUM_TEXTURES)
  42. video->cur_texture = 0;
  43. }
  44. static inline void render_video(struct obs_core_video *video, bool raw_active,
  45. const bool gpu_active, int cur_texture)
  46. {
  47. gs_begin_scene();
  48. gs_enable_depth_test(false);
  49. gs_set_cull_mode(GS_NEITHER);
  50. render_main_texture(video); // 渲染所有source到画布上
  51. if (raw_active || gpu_active) {
  52. // 将画布上的数据拷贝到纹理中, 此处格式是GS_RGBA
  53. // 画布对象是struct obs_core_video::render_texture
  54. // 如果画布宽高和output宽高一致,则返回的是obs_core_video::render_texture,
  55. // 否则在obs_core_video::output_texture对render_texture做缩放后 返回output_texture
  56. gs_texture_t *texture = render_output_texture(video);
  57. #ifdef _WIN32
  58. if (gpu_active)
  59. gs_flush();
  60. #endif
  61. if (video->gpu_conversion)// GPU格式转换
  62. render_convert_texture(video, texture);
  63. #ifdef _WIN32
  64. if (gpu_active) {
  65. gs_flush();
  66. output_gpu_encoders(video, raw_active);
  67. }
  68. #endif
  69. // 如果有录像或直播 将GPU转换后的纹理保存下来
  70. // GPU转换后的纹理 存储在struct obs_core_video::convert_textures
  71. // 数据保存在obs_core_video::copy_surfaces
  72. if (raw_active)
  73. stage_output_texture(video, cur_texture);
  74. }
  75. gs_set_render_target(NULL, NULL);
  76. gs_enable_blending(true);
  77. gs_end_scene();
  78. }

其中render_main_texture会调用obs_view_render,渲染所有channel:

  1. #define MAX_CHANNELS 64
  2. void obs_view_render(obs_view_t *view)
  3. {
  4. if (!view)
  5. return;
  6. pthread_mutex_lock(&view->channels_mutex);
  7. for (size_t i = 0; i < MAX_CHANNELS; i++)
  8. {
  9. struct obs_source *source = view->channels[i];
  10. if (source)
  11. {
  12. if (source->removed)
  13. {
  14. obs_source_release(source);
  15. view->channels[i] = NULL;
  16. }
  17. else
  18. {
  19. // 默认情况下 每次循环此处逻辑会进入三次
  20. // 一次是当前应用的transition source
  21. // 一次是OBS自动添加的was output audio (desktop audio)
  22. // 一次是OBS自动添加的was input audio(麦克风)
  23. // 但是后两个audio的channel 在调用obs_source_video_render时什么都不会做 直接return
  24. // 而transition source则会触发渲染当前scene 或做两个scene的转场
  25. obs_source_video_render(source);
  26. }
  27. }
  28. }
  29. pthread_mutex_unlock(&view->channels_mutex);
  30. }

遍历和渲染scene的每个source的函数是scene_video_render,代码如下:

  1. item = scene->first_item;
  2. while (item)
  3. {
  4. if (item->user_visible)
  5. render_item(item); // 会调到video_render
  6. item = item->next;
  7. }

static inline void render_item(struct obs_scene_item *item) 中设置每个source对应的矩阵matrix:

  1. gs_matrix_push(); // 保存之前的矩阵
  2. gs_matrix_mul(&item->draw_transform); // 设置sourceitem的矩阵
  3. if (item->item_render)
  4. {
  5. render_item_texture(item);
  6. }
  7. else
  8. {
  9. obs_source_video_render(item->source);
  10. }
  11. 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)关键函数如下:

  1. // 函数中又调用了obs_render_main_texture_internal
  2. // 之前已经把当前scene所有source画到obs_core_video::render_texture了
  3. // 直接将render_texture画到main窗口即可
  4. obs_render_main_texture_src_color_only();
  5. ===============================================
  6. if (window->IsPreviewProgramMode()) // studio mode
  7. {
  8. window->DrawBackdrop(float(ovi.base_width), float(ovi.base_height));
  9. OBSScene scene = window->GetCurrentScene();
  10. obs_source_t *source = obs_scene_get_source(scene);
  11. if (source)
  12. {
  13. // 渲染编辑中的临时场景
  14. // 主画布的数据是渲染的已应用的scene的画面
  15. // 这个scene source是编辑中的临时场景 所以需要重新渲染其中的source
  16. obs_source_video_render(source);
  17. }
  18. }
  19. else
  20. {
  21. // 在主屏幕画当前scene的所有source : 直接将主画布的纹理贴到display即可
  22. obs_render_main_texture_src_color_only();
  23. }
  24. // 画main中选中source的边框和鼠标悬停source的边框
  25. 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会通过回调把数据发出去:

  1. pthread_mutex_lock(&video->input_mutex);
  2. for (size_t i = 0; i < video->inputs.num; i++)
  3. {
  4. struct video_input *input = video->inputs.array + i;
  5. struct video_data frame = frame_info->frame;
  6. if (scale_video_output(input, &frame))
  7. input->callback(input->param, &frame);
  8. }
  9. pthread_mutex_unlock(&video->input_mutex);

录像和直播,回调函数都是:static void receive_video(void *param, struct video_data *frame)

该函数中 会调用do_encode对video编码(音频也是调用该函数进行编码)如果编码获取到packet 会发送到interleave_packets av交错排序保证时间戳单调性:

  1. bool do_encode(struct obs_encoder *encoder, struct encoder_frame *frame)
  2. {
  3. profile_start(do_encode_name);
  4. if (!encoder->profile_encoder_encode_name)
  5. encoder->profile_encoder_encode_name =
  6. profile_store_name(obs_get_profiler_name_store(),
  7. "encode(%s)", encoder->context.name);
  8. struct encoder_packet pkt = {0};
  9. bool received = false;
  10. bool success;
  11. pkt.timebase_num = encoder->timebase_num;
  12. pkt.timebase_den = encoder->timebase_den;
  13. pkt.encoder = encoder;
  14. profile_start(encoder->profile_encoder_encode_name);
  15. success = encoder->info.encode(encoder->context.data, frame, &pkt,&received);
  16. profile_end(encoder->profile_encoder_encode_name);
  17. send_off_encoder_packet(encoder, success, received, &pkt); // interleave_packets
  18. profile_end(do_encode_name);
  19. return success;
  20. }

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/194961
推荐阅读
相关标签
  

闽ICP备14008679号