当前位置:   article > 正文

obs源码简析之推流_obs推流 代码

obs推流 代码

写在最前面

obs录制推流,为obs核心给功能。

具体分析

obs点击开始录制,将会调用ui部分读取设置的参数:

UI/window-basic-main-outputs.cpp

  1. bool AdvancedOutput::StartRecording()
  2. {
  3. const char *path;
  4. const char *recFormat;
  5. const char *filenameFormat;
  6. bool noSpace = false;
  7. bool overwriteIfExists = false;
  8. if (!useStreamEncoder) {
  9. if (!ffmpegOutput) {
  10. UpdateRecordingSettings();
  11. }
  12. } else if (!obs_output_active(streamOutput)) {
  13. UpdateStreamSettings();
  14. }
  15. UpdateAudioSettings();
  16. if (!Active())
  17. SetupOutputs();
  18. if (!ffmpegOutput || ffmpegRecording) {
  19. path = config_get_string(main->Config(), "AdvOut",
  20. ffmpegRecording ? "FFFilePath" : "RecFilePath");
  21. recFormat = config_get_string(main->Config(), "AdvOut",
  22. ffmpegRecording ? "FFExtension" : "RecFormat");
  23. filenameFormat = config_get_string(main->Config(), "Output",
  24. "FilenameFormatting");
  25. overwriteIfExists = config_get_bool(main->Config(), "Output",
  26. "OverwriteIfExists");
  27. noSpace = config_get_bool(main->Config(), "AdvOut",
  28. ffmpegRecording ?
  29. "FFFileNameWithoutSpace" :
  30. "RecFileNameWithoutSpace");
  31. os_dir_t *dir = path && path[0] ? os_opendir(path) : nullptr;
  32. if (!dir) {
  33. if (main->isVisible())
  34. OBSMessageBox::information(main,
  35. QTStr("Output.BadPath.Title"),
  36. QTStr("Output.BadPath.Text"));
  37. else
  38. main->SysTrayNotify(QTStr("Output.BadPath.Text"),
  39. QSystemTrayIcon::Warning);
  40. return false;
  41. }
  42. os_closedir(dir);
  43. string strPath;
  44. strPath += path;
  45. char lastChar = strPath.back();
  46. if (lastChar != '/' && lastChar != '\\')
  47. strPath += "/";
  48. strPath += GenerateSpecifiedFilename(recFormat, noSpace,
  49. filenameFormat);
  50. ensure_directory_exists(strPath);
  51. if (!overwriteIfExists)
  52. FindBestFilename(strPath, noSpace);
  53. obs_data_t *settings = obs_data_create();
  54. obs_data_set_string(settings,
  55. ffmpegRecording ? "url" : "path",
  56. strPath.c_str());
  57. obs_output_update(fileOutput, settings);
  58. obs_data_release(settings);
  59. }
  60. if (!obs_output_start(fileOutput)) {
  61. QString error_reason;
  62. const char *error = obs_output_get_last_error(fileOutput);
  63. if (error)
  64. error_reason = QT_UTF8(error);
  65. else
  66. error_reason = QTStr("Output.StartFailedGeneric");
  67. QMessageBox::critical(main,
  68. QTStr("Output.StartRecordingFailed"),
  69. error_reason);
  70. return false;
  71. }
  72. return true;
  73. }

读取完毕后,将参数应用于obs同时修改视频编码和音频编码设置,同时更新obs程序运行参数

  1. inline void AdvancedOutput::SetupFFmpeg()
  2. {
  3. const char *url = config_get_string(main->Config(), "AdvOut", "FFURL");
  4. int vBitrate = config_get_int(main->Config(), "AdvOut", "FFVBitrate");
  5. int gopSize = config_get_int(main->Config(), "AdvOut", "FFVGOPSize");
  6. bool rescale = config_get_bool(main->Config(), "AdvOut", "FFRescale");
  7. const char *rescaleRes =
  8. config_get_string(main->Config(), "AdvOut", "FFRescaleRes");
  9. const char *formatName =
  10. config_get_string(main->Config(), "AdvOut", "FFFormat");
  11. const char *mimeType =
  12. config_get_string(main->Config(), "AdvOut", "FFFormatMimeType");
  13. const char *muxCustom =
  14. config_get_string(main->Config(), "AdvOut", "FFMCustom");
  15. const char *vEncoder =
  16. config_get_string(main->Config(), "AdvOut", "FFVEncoder");
  17. int vEncoderId =
  18. config_get_int(main->Config(), "AdvOut", "FFVEncoderId");
  19. const char *vEncCustom =
  20. config_get_string(main->Config(), "AdvOut", "FFVCustom");
  21. int aBitrate = config_get_int(main->Config(), "AdvOut", "FFABitrate");
  22. int aMixes = config_get_int(main->Config(), "AdvOut", "FFAudioMixes");
  23. const char *aEncoder =
  24. config_get_string(main->Config(), "AdvOut", "FFAEncoder");
  25. int aEncoderId =
  26. config_get_int(main->Config(), "AdvOut", "FFAEncoderId");
  27. const char *aEncCustom =
  28. config_get_string(main->Config(), "AdvOut", "FFACustom");
  29. obs_data_t *settings = obs_data_create();
  30. obs_data_set_string(settings, "url", url);
  31. obs_data_set_string(settings, "format_name", formatName);
  32. obs_data_set_string(settings, "format_mime_type", mimeType);
  33. obs_data_set_string(settings, "muxer_settings", muxCustom);
  34. obs_data_set_int(settings, "gop_size", gopSize);
  35. obs_data_set_int(settings, "video_bitrate", vBitrate);
  36. obs_data_set_string(settings, "video_encoder", vEncoder);
  37. obs_data_set_int(settings, "video_encoder_id", vEncoderId);
  38. obs_data_set_string(settings, "video_settings", vEncCustom);
  39. obs_data_set_int(settings, "audio_bitrate", aBitrate);
  40. obs_data_set_string(settings, "audio_encoder", aEncoder);
  41. obs_data_set_int(settings, "audio_encoder_id", aEncoderId);
  42. obs_data_set_string(settings, "audio_settings", aEncCustom);
  43. if (rescale && rescaleRes && *rescaleRes) {
  44. int width;
  45. int height;
  46. int val = sscanf(rescaleRes, "%dx%d", &width, &height);
  47. if (val == 2 && width && height) {
  48. obs_data_set_int(settings, "scale_width", width);
  49. obs_data_set_int(settings, "scale_height", height);
  50. }
  51. }
  52. obs_output_set_mixers(fileOutput, aMixes);
  53. obs_output_set_media(fileOutput, obs_get_video(), obs_get_audio());
  54. obs_output_update(fileOutput, settings);
  55. obs_data_release(settings);
  56. }

参数设置成功后开始准备输出

libobs/obs-output.c

  1. bool obs_output_start(obs_output_t *output)
  2. {
  3. bool encoded;
  4. bool has_service;
  5. if (!obs_output_valid(output, "obs_output_start"))
  6. return false;
  7. if (!output->context.data)
  8. return false;
  9. has_service = (output->info.flags & OBS_OUTPUT_SERVICE) != 0;
  10. if (has_service && !obs_service_initialize(output->service, output))
  11. return false;
  12. encoded = (output->info.flags & OBS_OUTPUT_ENCODED) != 0;
  13. if (encoded && output->delay_sec) {
  14. return obs_output_delay_start(output);
  15. } else {
  16. if (obs_output_actual_start(output)) {
  17. do_output_signal(output, "starting");
  18. return true;
  19. }
  20. return false;
  21. }
  22. }

输出流准备好了之后,创建输出函数线程,开始调用ffmpeg有关函数准备输出。

obs-ffmpeg/obs-ffmpeg-output.c

  1. static bool ffmpeg_output_start(void *data)
  2. {
  3. struct ffmpeg_output *output = data;
  4. int ret;
  5. if (output->connecting)
  6. return false;
  7. os_atomic_set_bool(&output->stopping, false);
  8. output->audio_start_ts = 0;
  9. output->video_start_ts = 0;
  10. output->total_bytes = 0;
  11. ret = pthread_create(&output->start_thread, NULL, start_thread, output);
  12. return (output->connecting = (ret == 0));
  13. }

接着创建ffmpeg输出,设置输出参数

  1. int avformat_alloc_output_context2(AVFormatContext **ctx, AVOutputFormat *oformat,
  2. const char *format_name, const char *filename);

上面是调用ffmpeg关键代码设置输出方式,如rtp,rtmp等。完整代码:

  1. static bool ffmpeg_data_init(struct ffmpeg_data *data,
  2. struct ffmpeg_cfg *config)
  3. {
  4. bool is_rtmp = false;
  5. memset(data, 0, sizeof(struct ffmpeg_data));
  6. data->config = *config;
  7. data->num_audio_streams = config->audio_mix_count;
  8. data->audio_tracks = config->audio_tracks;
  9. if (!config->url || !*config->url)
  10. return false;
  11. #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100)
  12. av_register_all();
  13. #endif
  14. avformat_network_init();
  15. is_rtmp = (astrcmpi_n(config->url, "rtmp://", 7) == 0);
  16. AVOutputFormat *output_format = av_guess_format(
  17. is_rtmp ? "flv" : data->config.format_name, data->config.url,
  18. is_rtmp ? NULL : data->config.format_mime_type);
  19. if (output_format == NULL) {
  20. ffmpeg_log_error(
  21. LOG_WARNING, data,
  22. "Couldn't find matching output format with "
  23. "parameters: name=%s, url=%s, mime=%s",
  24. safe_str(is_rtmp ? "flv" : data->config.format_name),
  25. safe_str(data->config.url),
  26. safe_str(is_rtmp ? NULL
  27. : data->config.format_mime_type));
  28. goto fail;
  29. }
  30. avformat_alloc_output_context2(&data->output, output_format, NULL,
  31. NULL);
  32. if (!data->output) {
  33. ffmpeg_log_error(LOG_WARNING, data,
  34. "Couldn't create avformat context");
  35. goto fail;
  36. }
  37. if (is_rtmp) {
  38. data->output->oformat->video_codec = AV_CODEC_ID_H264;
  39. data->output->oformat->audio_codec = AV_CODEC_ID_AAC;
  40. } else {
  41. if (data->config.format_name)
  42. set_encoder_ids(data);
  43. }
  44. if (!init_streams(data))
  45. goto fail;
  46. if (!open_output_file(data))
  47. goto fail;
  48. av_dump_format(data->output, 0, NULL, 1);
  49. data->initialized = true;
  50. return true;
  51. fail:
  52. blog(LOG_WARNING, "ffmpeg_data_init failed");
  53. return false;
  54. }

写入文件头,调用ffmpeg函数代码为:

ret = avformat_write_header(data->output, &dict);

完整代码:

  1. static inline bool open_output_file(struct ffmpeg_data *data)
  2. {
  3. AVOutputFormat *format = data->output->oformat;
  4. int ret;
  5. AVDictionary *dict = NULL;
  6. if ((ret = av_dict_parse_string(&dict, data->config.muxer_settings, "=",
  7. " ", 0))) {
  8. ffmpeg_log_error(LOG_WARNING, data,
  9. "Failed to parse muxer settings: %s\n%s",
  10. av_err2str(ret), data->config.muxer_settings);
  11. av_dict_free(&dict);
  12. return false;
  13. }
  14. if (av_dict_count(dict) > 0) {
  15. struct dstr str = {0};
  16. AVDictionaryEntry *entry = NULL;
  17. while ((entry = av_dict_get(dict, "", entry,
  18. AV_DICT_IGNORE_SUFFIX)))
  19. dstr_catf(&str, "\n\t%s=%s", entry->key, entry->value);
  20. blog(LOG_INFO, "Using muxer settings: %s", str.array);
  21. dstr_free(&str);
  22. }
  23. if ((format->flags & AVFMT_NOFILE) == 0) {
  24. ret = avio_open2(&data->output->pb, data->config.url,
  25. AVIO_FLAG_WRITE, NULL, &dict);
  26. if (ret < 0) {
  27. ffmpeg_log_error(LOG_WARNING, data,
  28. "Couldn't open '%s', %s",
  29. data->config.url, av_err2str(ret));
  30. av_dict_free(&dict);
  31. return false;
  32. }
  33. }
  34. strncpy(data->output->filename, data->config.url,
  35. sizeof(data->output->filename));
  36. data->output->filename[sizeof(data->output->filename) - 1] = 0;
  37. ret = avformat_write_header(data->output, &dict);
  38. if (ret < 0) {
  39. ffmpeg_log_error(LOG_WARNING, data, "Error opening '%s': %s",
  40. data->config.url, av_err2str(ret));
  41. return false;
  42. }
  43. if (av_dict_count(dict) > 0) {
  44. struct dstr str = {0};
  45. AVDictionaryEntry *entry = NULL;
  46. while ((entry = av_dict_get(dict, "", entry,
  47. AV_DICT_IGNORE_SUFFIX)))
  48. dstr_catf(&str, "\n\t%s=%s", entry->key, entry->value);
  49. blog(LOG_INFO, "Invalid muxer settings: %s", str.array);
  50. dstr_free(&str);
  51. }
  52. av_dict_free(&dict);
  53. return true;
  54. }

创建数据线程,设置视频流,音频流编码,然后捕获数据流开始输出

  1. static bool try_connect(struct ffmpeg_output *output)
  2. {
  3. video_t *video = obs_output_video(output->output);
  4. const struct video_output_info *voi = video_output_get_info(video);
  5. struct ffmpeg_cfg config;
  6. obs_data_t *settings;
  7. bool success;
  8. int ret;
  9. settings = obs_output_get_settings(output->output);
  10. obs_data_set_default_int(settings, "gop_size", 120);
  11. config.url = obs_data_get_string(settings, "url");
  12. config.format_name = get_string_or_null(settings, "format_name");
  13. config.format_mime_type =
  14. get_string_or_null(settings, "format_mime_type");
  15. config.muxer_settings = obs_data_get_string(settings, "muxer_settings");
  16. config.video_bitrate = (int)obs_data_get_int(settings, "video_bitrate");
  17. config.audio_bitrate = (int)obs_data_get_int(settings, "audio_bitrate");
  18. config.gop_size = (int)obs_data_get_int(settings, "gop_size");
  19. config.video_encoder = get_string_or_null(settings, "video_encoder");
  20. config.video_encoder_id =
  21. (int)obs_data_get_int(settings, "video_encoder_id");
  22. config.audio_encoder = get_string_or_null(settings, "audio_encoder");
  23. config.audio_encoder_id =
  24. (int)obs_data_get_int(settings, "audio_encoder_id");
  25. config.video_settings = obs_data_get_string(settings, "video_settings");
  26. config.audio_settings = obs_data_get_string(settings, "audio_settings");
  27. config.scale_width = (int)obs_data_get_int(settings, "scale_width");
  28. config.scale_height = (int)obs_data_get_int(settings, "scale_height");
  29. config.width = (int)obs_output_get_width(output->output);
  30. config.height = (int)obs_output_get_height(output->output);
  31. config.format =
  32. obs_to_ffmpeg_video_format(video_output_get_format(video));
  33. config.audio_tracks = (int)obs_output_get_mixers(output->output);
  34. config.audio_mix_count = get_audio_mix_count(config.audio_tracks);
  35. if (format_is_yuv(voi->format)) {
  36. config.color_range = voi->range == VIDEO_RANGE_FULL
  37. ? AVCOL_RANGE_JPEG
  38. : AVCOL_RANGE_MPEG;
  39. config.color_space = voi->colorspace == VIDEO_CS_709
  40. ? AVCOL_SPC_BT709
  41. : AVCOL_SPC_BT470BG;
  42. } else {
  43. config.color_range = AVCOL_RANGE_UNSPECIFIED;
  44. config.color_space = AVCOL_SPC_RGB;
  45. }
  46. if (config.format == AV_PIX_FMT_NONE) {
  47. blog(LOG_DEBUG, "invalid pixel format used for FFmpeg output");
  48. return false;
  49. }
  50. if (!config.scale_width)
  51. config.scale_width = config.width;
  52. if (!config.scale_height)
  53. config.scale_height = config.height;
  54. success = ffmpeg_data_init(&output->ff_data, &config);
  55. obs_data_release(settings);
  56. if (!success) {
  57. if (output->ff_data.last_error) {
  58. obs_output_set_last_error(output->output,
  59. output->ff_data.last_error);
  60. }
  61. ffmpeg_data_free(&output->ff_data);
  62. return false;
  63. }
  64. struct audio_convert_info aci = {.format =
  65. output->ff_data.audio_format};
  66. output->active = true;
  67. if (!obs_output_can_begin_data_capture(output->output, 0))
  68. return false;
  69. ret = pthread_create(&output->write_thread, NULL, write_thread, output);
  70. if (ret != 0) {
  71. ffmpeg_log_error(LOG_WARNING, &output->ff_data,
  72. "ffmpeg_output_start: failed to create write "
  73. "thread.");
  74. ffmpeg_output_full_stop(output);
  75. return false;
  76. }
  77. obs_output_set_video_conversion(output->output, NULL);
  78. obs_output_set_audio_conversion(output->output, &aci);
  79. obs_output_begin_data_capture(output->output, 0);
  80. output->write_thread_active = true;
  81. return true;
  82. }

线程设置完毕后,开始通过线程写入数据,输出

  1. static void *write_thread(void *data)
  2. {
  3. struct ffmpeg_output *output = data;
  4. while (os_sem_wait(output->write_sem) == 0) {
  5. /* check to see if shutting down */
  6. if (os_event_try(output->stop_event) == 0)
  7. break;
  8. int ret = process_packet(output);
  9. if (ret != 0) {
  10. int code = OBS_OUTPUT_ERROR;
  11. pthread_detach(output->write_thread);
  12. output->write_thread_active = false;
  13. if (ret == -ENOSPC)
  14. code = OBS_OUTPUT_NO_SPACE;
  15. obs_output_signal_stop(output->output, code);
  16. ffmpeg_deactivate(output);
  17. break;
  18. }
  19. }
  20. output->active = false;
  21. return NULL;
  22. }

线程将反复调用下面函数处理数据包,注意这里线程有互斥锁判断,修改不当容易造成程序崩溃。

  1. static int process_packet(struct ffmpeg_output *output)
  2. {
  3. AVPacket packet;
  4. bool new_packet = false;
  5. int ret;
  6. pthread_mutex_lock(&output->write_mutex);
  7. if (output->packets.num) {
  8. packet = output->packets.array[0];
  9. da_erase(output->packets, 0);
  10. new_packet = true;
  11. }
  12. pthread_mutex_unlock(&output->write_mutex);
  13. if (!new_packet)
  14. return 0;
  15. /*blog(LOG_DEBUG, "size = %d, flags = %lX, stream = %d, "
  16. "packets queued: %lu",
  17. packet.size, packet.flags,
  18. packet.stream_index, output->packets.num);*/
  19. if (stopping(output)) {
  20. uint64_t sys_ts = get_packet_sys_dts(output, &packet);
  21. if (sys_ts >= output->stop_ts) {
  22. ffmpeg_output_full_stop(output);
  23. return 0;
  24. }
  25. }
  26. output->total_bytes += packet.size;
  27. ret = av_interleaved_write_frame(output->ff_data.output, &packet);
  28. if (ret < 0) {
  29. av_free_packet(&packet);
  30. ffmpeg_log_error(LOG_WARNING, &output->ff_data,
  31. "receive_audio: Error writing packet: %s",
  32. av_err2str(ret));
  33. return ret;
  34. }
  35. return 0;
  36. }

点击停止录制后,线程将会一步步释放资源。工作涉及整个流程大体如上

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

闽ICP备14008679号