赞
踩
写在最前面
obs录制推流,为obs核心给功能。
具体分析
obs点击开始录制,将会调用ui部分读取设置的参数:
UI/window-basic-main-outputs.cpp
- bool AdvancedOutput::StartRecording()
- {
- const char *path;
- const char *recFormat;
- const char *filenameFormat;
- bool noSpace = false;
- bool overwriteIfExists = false;
-
- if (!useStreamEncoder) {
- if (!ffmpegOutput) {
- UpdateRecordingSettings();
- }
- } else if (!obs_output_active(streamOutput)) {
- UpdateStreamSettings();
- }
-
- UpdateAudioSettings();
-
- if (!Active())
- SetupOutputs();
-
- if (!ffmpegOutput || ffmpegRecording) {
- path = config_get_string(main->Config(), "AdvOut",
- ffmpegRecording ? "FFFilePath" : "RecFilePath");
- recFormat = config_get_string(main->Config(), "AdvOut",
- ffmpegRecording ? "FFExtension" : "RecFormat");
- filenameFormat = config_get_string(main->Config(), "Output",
- "FilenameFormatting");
- overwriteIfExists = config_get_bool(main->Config(), "Output",
- "OverwriteIfExists");
- noSpace = config_get_bool(main->Config(), "AdvOut",
- ffmpegRecording ?
- "FFFileNameWithoutSpace" :
- "RecFileNameWithoutSpace");
-
- os_dir_t *dir = path && path[0] ? os_opendir(path) : nullptr;
-
- if (!dir) {
- if (main->isVisible())
- OBSMessageBox::information(main,
- QTStr("Output.BadPath.Title"),
- QTStr("Output.BadPath.Text"));
- else
- main->SysTrayNotify(QTStr("Output.BadPath.Text"),
- QSystemTrayIcon::Warning);
- return false;
- }
-
- os_closedir(dir);
-
- string strPath;
- strPath += path;
-
- char lastChar = strPath.back();
- if (lastChar != '/' && lastChar != '\\')
- strPath += "/";
-
- strPath += GenerateSpecifiedFilename(recFormat, noSpace,
- filenameFormat);
- ensure_directory_exists(strPath);
- if (!overwriteIfExists)
- FindBestFilename(strPath, noSpace);
-
- obs_data_t *settings = obs_data_create();
- obs_data_set_string(settings,
- ffmpegRecording ? "url" : "path",
- strPath.c_str());
-
- obs_output_update(fileOutput, settings);
-
- obs_data_release(settings);
- }
-
- if (!obs_output_start(fileOutput)) {
- QString error_reason;
- const char *error = obs_output_get_last_error(fileOutput);
- if (error)
- error_reason = QT_UTF8(error);
- else
- error_reason = QTStr("Output.StartFailedGeneric");
- QMessageBox::critical(main,
- QTStr("Output.StartRecordingFailed"),
- error_reason);
- return false;
- }
-
- return true;
- }

读取完毕后,将参数应用于obs同时修改视频编码和音频编码设置,同时更新obs程序运行参数
- inline void AdvancedOutput::SetupFFmpeg()
- {
- const char *url = config_get_string(main->Config(), "AdvOut", "FFURL");
- int vBitrate = config_get_int(main->Config(), "AdvOut", "FFVBitrate");
- int gopSize = config_get_int(main->Config(), "AdvOut", "FFVGOPSize");
- bool rescale = config_get_bool(main->Config(), "AdvOut", "FFRescale");
- const char *rescaleRes =
- config_get_string(main->Config(), "AdvOut", "FFRescaleRes");
- const char *formatName =
- config_get_string(main->Config(), "AdvOut", "FFFormat");
- const char *mimeType =
- config_get_string(main->Config(), "AdvOut", "FFFormatMimeType");
- const char *muxCustom =
- config_get_string(main->Config(), "AdvOut", "FFMCustom");
- const char *vEncoder =
- config_get_string(main->Config(), "AdvOut", "FFVEncoder");
- int vEncoderId =
- config_get_int(main->Config(), "AdvOut", "FFVEncoderId");
- const char *vEncCustom =
- config_get_string(main->Config(), "AdvOut", "FFVCustom");
- int aBitrate = config_get_int(main->Config(), "AdvOut", "FFABitrate");
- int aMixes = config_get_int(main->Config(), "AdvOut", "FFAudioMixes");
- const char *aEncoder =
- config_get_string(main->Config(), "AdvOut", "FFAEncoder");
- int aEncoderId =
- config_get_int(main->Config(), "AdvOut", "FFAEncoderId");
- const char *aEncCustom =
- config_get_string(main->Config(), "AdvOut", "FFACustom");
- obs_data_t *settings = obs_data_create();
-
- obs_data_set_string(settings, "url", url);
- obs_data_set_string(settings, "format_name", formatName);
- obs_data_set_string(settings, "format_mime_type", mimeType);
- obs_data_set_string(settings, "muxer_settings", muxCustom);
- obs_data_set_int(settings, "gop_size", gopSize);
- obs_data_set_int(settings, "video_bitrate", vBitrate);
- obs_data_set_string(settings, "video_encoder", vEncoder);
- obs_data_set_int(settings, "video_encoder_id", vEncoderId);
- obs_data_set_string(settings, "video_settings", vEncCustom);
- obs_data_set_int(settings, "audio_bitrate", aBitrate);
- obs_data_set_string(settings, "audio_encoder", aEncoder);
- obs_data_set_int(settings, "audio_encoder_id", aEncoderId);
- obs_data_set_string(settings, "audio_settings", aEncCustom);
-
- if (rescale && rescaleRes && *rescaleRes) {
- int width;
- int height;
- int val = sscanf(rescaleRes, "%dx%d", &width, &height);
-
- if (val == 2 && width && height) {
- obs_data_set_int(settings, "scale_width", width);
- obs_data_set_int(settings, "scale_height", height);
- }
- }
-
- obs_output_set_mixers(fileOutput, aMixes);
- obs_output_set_media(fileOutput, obs_get_video(), obs_get_audio());
- obs_output_update(fileOutput, settings);
-
- obs_data_release(settings);
- }

参数设置成功后开始准备输出
libobs/obs-output.c
- bool obs_output_start(obs_output_t *output)
- {
- bool encoded;
- bool has_service;
- if (!obs_output_valid(output, "obs_output_start"))
- return false;
- if (!output->context.data)
- return false;
-
- has_service = (output->info.flags & OBS_OUTPUT_SERVICE) != 0;
- if (has_service && !obs_service_initialize(output->service, output))
- return false;
-
- encoded = (output->info.flags & OBS_OUTPUT_ENCODED) != 0;
- if (encoded && output->delay_sec) {
- return obs_output_delay_start(output);
- } else {
- if (obs_output_actual_start(output)) {
- do_output_signal(output, "starting");
- return true;
- }
-
- return false;
- }
- }

输出流准备好了之后,创建输出函数线程,开始调用ffmpeg有关函数准备输出。
obs-ffmpeg/obs-ffmpeg-output.c
- static bool ffmpeg_output_start(void *data)
- {
- struct ffmpeg_output *output = data;
- int ret;
-
- if (output->connecting)
- return false;
-
- os_atomic_set_bool(&output->stopping, false);
- output->audio_start_ts = 0;
- output->video_start_ts = 0;
- output->total_bytes = 0;
-
- ret = pthread_create(&output->start_thread, NULL, start_thread, output);
- return (output->connecting = (ret == 0));
- }

接着创建ffmpeg输出,设置输出参数
- int avformat_alloc_output_context2(AVFormatContext **ctx, AVOutputFormat *oformat,
- const char *format_name, const char *filename);
上面是调用ffmpeg关键代码设置输出方式,如rtp,rtmp等。完整代码:
- static bool ffmpeg_data_init(struct ffmpeg_data *data,
- struct ffmpeg_cfg *config)
- {
- bool is_rtmp = false;
-
- memset(data, 0, sizeof(struct ffmpeg_data));
- data->config = *config;
- data->num_audio_streams = config->audio_mix_count;
- data->audio_tracks = config->audio_tracks;
- if (!config->url || !*config->url)
- return false;
-
- #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100)
- av_register_all();
- #endif
- avformat_network_init();
-
- is_rtmp = (astrcmpi_n(config->url, "rtmp://", 7) == 0);
-
- AVOutputFormat *output_format = av_guess_format(
- is_rtmp ? "flv" : data->config.format_name, data->config.url,
- is_rtmp ? NULL : data->config.format_mime_type);
-
- if (output_format == NULL) {
- ffmpeg_log_error(
- LOG_WARNING, data,
- "Couldn't find matching output format with "
- "parameters: name=%s, url=%s, mime=%s",
- safe_str(is_rtmp ? "flv" : data->config.format_name),
- safe_str(data->config.url),
- safe_str(is_rtmp ? NULL
- : data->config.format_mime_type));
-
- goto fail;
- }
-
- avformat_alloc_output_context2(&data->output, output_format, NULL,
- NULL);
-
- if (!data->output) {
- ffmpeg_log_error(LOG_WARNING, data,
- "Couldn't create avformat context");
- goto fail;
- }
-
- if (is_rtmp) {
- data->output->oformat->video_codec = AV_CODEC_ID_H264;
- data->output->oformat->audio_codec = AV_CODEC_ID_AAC;
- } else {
- if (data->config.format_name)
- set_encoder_ids(data);
- }
-
- if (!init_streams(data))
- goto fail;
- if (!open_output_file(data))
- goto fail;
-
- av_dump_format(data->output, 0, NULL, 1);
-
- data->initialized = true;
- return true;
-
- fail:
- blog(LOG_WARNING, "ffmpeg_data_init failed");
- return false;
- }

写入文件头,调用ffmpeg函数代码为:
ret = avformat_write_header(data->output, &dict);
完整代码:
- static inline bool open_output_file(struct ffmpeg_data *data)
- {
- AVOutputFormat *format = data->output->oformat;
- int ret;
-
- AVDictionary *dict = NULL;
- if ((ret = av_dict_parse_string(&dict, data->config.muxer_settings, "=",
- " ", 0))) {
- ffmpeg_log_error(LOG_WARNING, data,
- "Failed to parse muxer settings: %s\n%s",
- av_err2str(ret), data->config.muxer_settings);
-
- av_dict_free(&dict);
- return false;
- }
-
- if (av_dict_count(dict) > 0) {
- struct dstr str = {0};
-
- AVDictionaryEntry *entry = NULL;
- while ((entry = av_dict_get(dict, "", entry,
- AV_DICT_IGNORE_SUFFIX)))
- dstr_catf(&str, "\n\t%s=%s", entry->key, entry->value);
-
- blog(LOG_INFO, "Using muxer settings: %s", str.array);
- dstr_free(&str);
- }
-
- if ((format->flags & AVFMT_NOFILE) == 0) {
- ret = avio_open2(&data->output->pb, data->config.url,
- AVIO_FLAG_WRITE, NULL, &dict);
- if (ret < 0) {
- ffmpeg_log_error(LOG_WARNING, data,
- "Couldn't open '%s', %s",
- data->config.url, av_err2str(ret));
- av_dict_free(&dict);
- return false;
- }
- }
-
- strncpy(data->output->filename, data->config.url,
- sizeof(data->output->filename));
- data->output->filename[sizeof(data->output->filename) - 1] = 0;
-
- ret = avformat_write_header(data->output, &dict);
- if (ret < 0) {
- ffmpeg_log_error(LOG_WARNING, data, "Error opening '%s': %s",
- data->config.url, av_err2str(ret));
- return false;
- }
-
- if (av_dict_count(dict) > 0) {
- struct dstr str = {0};
-
- AVDictionaryEntry *entry = NULL;
- while ((entry = av_dict_get(dict, "", entry,
- AV_DICT_IGNORE_SUFFIX)))
- dstr_catf(&str, "\n\t%s=%s", entry->key, entry->value);
-
- blog(LOG_INFO, "Invalid muxer settings: %s", str.array);
- dstr_free(&str);
- }
-
- av_dict_free(&dict);
-
- return true;
- }

创建数据线程,设置视频流,音频流编码,然后捕获数据流开始输出
- static bool try_connect(struct ffmpeg_output *output)
- {
- video_t *video = obs_output_video(output->output);
- const struct video_output_info *voi = video_output_get_info(video);
- struct ffmpeg_cfg config;
- obs_data_t *settings;
- bool success;
- int ret;
-
- settings = obs_output_get_settings(output->output);
-
- obs_data_set_default_int(settings, "gop_size", 120);
-
- config.url = obs_data_get_string(settings, "url");
- config.format_name = get_string_or_null(settings, "format_name");
- config.format_mime_type =
- get_string_or_null(settings, "format_mime_type");
- config.muxer_settings = obs_data_get_string(settings, "muxer_settings");
- config.video_bitrate = (int)obs_data_get_int(settings, "video_bitrate");
- config.audio_bitrate = (int)obs_data_get_int(settings, "audio_bitrate");
- config.gop_size = (int)obs_data_get_int(settings, "gop_size");
- config.video_encoder = get_string_or_null(settings, "video_encoder");
- config.video_encoder_id =
- (int)obs_data_get_int(settings, "video_encoder_id");
- config.audio_encoder = get_string_or_null(settings, "audio_encoder");
- config.audio_encoder_id =
- (int)obs_data_get_int(settings, "audio_encoder_id");
- config.video_settings = obs_data_get_string(settings, "video_settings");
- config.audio_settings = obs_data_get_string(settings, "audio_settings");
- config.scale_width = (int)obs_data_get_int(settings, "scale_width");
- config.scale_height = (int)obs_data_get_int(settings, "scale_height");
- config.width = (int)obs_output_get_width(output->output);
- config.height = (int)obs_output_get_height(output->output);
- config.format =
- obs_to_ffmpeg_video_format(video_output_get_format(video));
- config.audio_tracks = (int)obs_output_get_mixers(output->output);
- config.audio_mix_count = get_audio_mix_count(config.audio_tracks);
-
- if (format_is_yuv(voi->format)) {
- config.color_range = voi->range == VIDEO_RANGE_FULL
- ? AVCOL_RANGE_JPEG
- : AVCOL_RANGE_MPEG;
- config.color_space = voi->colorspace == VIDEO_CS_709
- ? AVCOL_SPC_BT709
- : AVCOL_SPC_BT470BG;
- } else {
- config.color_range = AVCOL_RANGE_UNSPECIFIED;
- config.color_space = AVCOL_SPC_RGB;
- }
-
- if (config.format == AV_PIX_FMT_NONE) {
- blog(LOG_DEBUG, "invalid pixel format used for FFmpeg output");
- return false;
- }
-
- if (!config.scale_width)
- config.scale_width = config.width;
- if (!config.scale_height)
- config.scale_height = config.height;
-
- success = ffmpeg_data_init(&output->ff_data, &config);
- obs_data_release(settings);
-
- if (!success) {
- if (output->ff_data.last_error) {
- obs_output_set_last_error(output->output,
- output->ff_data.last_error);
- }
- ffmpeg_data_free(&output->ff_data);
- return false;
- }
-
- struct audio_convert_info aci = {.format =
- output->ff_data.audio_format};
-
- output->active = true;
-
- if (!obs_output_can_begin_data_capture(output->output, 0))
- return false;
-
- ret = pthread_create(&output->write_thread, NULL, write_thread, output);
- if (ret != 0) {
- ffmpeg_log_error(LOG_WARNING, &output->ff_data,
- "ffmpeg_output_start: failed to create write "
- "thread.");
- ffmpeg_output_full_stop(output);
- return false;
- }
-
- obs_output_set_video_conversion(output->output, NULL);
- obs_output_set_audio_conversion(output->output, &aci);
- obs_output_begin_data_capture(output->output, 0);
- output->write_thread_active = true;
- return true;
- }

线程设置完毕后,开始通过线程写入数据,输出
- static void *write_thread(void *data)
- {
- struct ffmpeg_output *output = data;
-
- while (os_sem_wait(output->write_sem) == 0) {
- /* check to see if shutting down */
- if (os_event_try(output->stop_event) == 0)
- break;
-
- int ret = process_packet(output);
- if (ret != 0) {
- int code = OBS_OUTPUT_ERROR;
-
- pthread_detach(output->write_thread);
- output->write_thread_active = false;
-
- if (ret == -ENOSPC)
- code = OBS_OUTPUT_NO_SPACE;
-
- obs_output_signal_stop(output->output, code);
- ffmpeg_deactivate(output);
- break;
- }
- }
-
- output->active = false;
- return NULL;
- }

线程将反复调用下面函数处理数据包,注意这里线程有互斥锁判断,修改不当容易造成程序崩溃。
- static int process_packet(struct ffmpeg_output *output)
- {
- AVPacket packet;
- bool new_packet = false;
- int ret;
-
- pthread_mutex_lock(&output->write_mutex);
- if (output->packets.num) {
- packet = output->packets.array[0];
- da_erase(output->packets, 0);
- new_packet = true;
- }
- pthread_mutex_unlock(&output->write_mutex);
-
- if (!new_packet)
- return 0;
-
- /*blog(LOG_DEBUG, "size = %d, flags = %lX, stream = %d, "
- "packets queued: %lu",
- packet.size, packet.flags,
- packet.stream_index, output->packets.num);*/
-
- if (stopping(output)) {
- uint64_t sys_ts = get_packet_sys_dts(output, &packet);
- if (sys_ts >= output->stop_ts) {
- ffmpeg_output_full_stop(output);
- return 0;
- }
- }
-
- output->total_bytes += packet.size;
-
- ret = av_interleaved_write_frame(output->ff_data.output, &packet);
- if (ret < 0) {
- av_free_packet(&packet);
- ffmpeg_log_error(LOG_WARNING, &output->ff_data,
- "receive_audio: Error writing packet: %s",
- av_err2str(ret));
- return ret;
- }
-
- return 0;
- }

点击停止录制后,线程将会一步步释放资源。工作涉及整个流程大体如上
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。