一 開始錄製按鈕
1) 托盤菜單
connect(sysTrayRecord, SIGNAL(triggered()),
this, SLOT(on_recordButton_clicked()));
void OBSBasic::SystemTrayInit()
{
trayIcon = new QSystemTrayIcon(QIcon(":/res/images/obs.png"),
this);
trayIcon->setToolTip("OBS Studio");
showHide = new QAction(QTStr("Basic.SystemTray.Show"),
trayIcon);
sysTrayStream = new QAction(QTStr("Basic.Main.StartStreaming"),
trayIcon);
sysTrayRecord = new QAction(QTStr("Basic.Main.StartRecording"),
trayIcon);
sysTrayReplayBuffer = new QAction(QTStr("Basic.Main.StartReplayBuffer"),
trayIcon);
exit = new QAction(QTStr("Exit"),
trayIcon);
if (outputHandler && !outputHandler->replayBuffer)
sysTrayReplayBuffer->setEnabled(false);
connect(trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
this,
SLOT(IconActivated(QSystemTrayIcon::ActivationReason)));
connect(showHide, SIGNAL(triggered()),
this, SLOT(ToggleShowHide()));
connect(sysTrayStream, SIGNAL(triggered()),
this, SLOT(on_streamButton_clicked()));
connect(sysTrayRecord, SIGNAL(triggered()),
this, SLOT(on_recordButton_clicked()));
connect(sysTrayReplayBuffer.data(), &QAction::triggered,
this, &OBSBasic::ReplayBufferClicked);
connect(exit, SIGNAL(triggered()),
this, SLOT(close()));
QMenu *previewProjector = new QMenu(QTStr("PreviewProjector"));
AddProjectorMenuMonitors(previewProjector, this,
SLOT(OpenPreviewProjector()));
trayMenu = new QMenu;
trayMenu->addAction(showHide);
trayMenu->addMenu(previewProjector);
trayMenu->addAction(sysTrayStream);
trayMenu->addAction(sysTrayRecord);
trayMenu->addAction(sysTrayReplayBuffer);
trayMenu->addAction(exit);
trayIcon->setContextMenu(trayMenu);
}
2) recordButton默認槽函數 on_recordButton_clicked()
3)開始錄製處理函數
void OBSBasic::on_recordButton_clicked()
{
if (outputHandler->RecordingActive())
StopRecording();
else
StartRecording();
}
二 視頻路徑
1)
C:\Users\Administrator\Videos
string GetDefaultVideoSavePath()
{
wchar_t path_utf16[MAX_PATH];
char path_utf8[MAX_PATH] = {};
SHGetFolderPathW(NULL, CSIDL_MYVIDEO, NULL, SHGFP_TYPE_CURRENT,
path_utf16);
os_wcs_to_utf8(path_utf16, wcslen(path_utf16), path_utf8, MAX_PATH);
return string(path_utf8);
}
2)獲得路徑
ConfigFile basicConfig; //默認配置
InitBasicConfigDefaults() 設置默認路徑
bool OBSBasic::InitBasicConfigDefaults()
{
QList<QScreen*> screens = QGuiApplication::screens();
if (!screens.size()) {
OBSErrorBox(NULL, "There appears to be no monitors. Er, this "
"technically shouldn't be possible.");
return false;
}
QScreen *primaryScreen = QGuiApplication::primaryScreen();
uint32_t cx = primaryScreen->size().width();
uint32_t cy = primaryScreen->size().height();
bool oldResolutionDefaults = config_get_bool(App()->GlobalConfig(),
"General", "Pre19Defaults");
/* use 1920x1080 for new default base res if main monitor is above
* 1920x1080, but don't apply for people from older builds -- only to
* new users */
if (!oldResolutionDefaults && (cx * cy) > (1920 * 1080)) {
cx = 1920;
cy = 1080;
}
/* ----------------------------------------------------- */
/* move over mixer values in advanced if older config */
if (config_has_user_value(basicConfig, "AdvOut", "RecTrackIndex") &&
!config_has_user_value(basicConfig, "AdvOut", "RecTracks")) {
uint64_t track = config_get_uint(basicConfig, "AdvOut",
"RecTrackIndex");
track = 1ULL << (track - 1);
config_set_uint(basicConfig, "AdvOut", "RecTracks", track);
config_remove_value(basicConfig, "AdvOut", "RecTrackIndex");
config_save_safe(basicConfig, "tmp", nullptr);
}
/* ----------------------------------------------------- */
config_set_default_string(basicConfig, "Output", "Mode", "Simple");
config_set_default_string(basicConfig, "SimpleOutput", "FilePath", //......
GetDefaultVideoSavePath().c_str());
config_set_default_string(basicConfig, "SimpleOutput", "RecFormat",
"flv");
config_set_default_uint (basicConfig, "SimpleOutput", "VBitrate",
2500);
config_set_default_string(basicConfig, "SimpleOutput", "StreamEncoder",
SIMPLE_ENCODER_X264);
config_set_default_uint (basicConfig, "SimpleOutput", "ABitrate", 160);
config_set_default_bool (basicConfig, "SimpleOutput", "UseAdvanced",
false);
config_set_default_bool (basicConfig, "SimpleOutput", "EnforceBitrate",
true);
config_set_default_string(basicConfig, "SimpleOutput", "Preset",
"veryfast");
config_set_default_string(basicConfig, "SimpleOutput", "RecQuality",
"Stream");
config_set_default_string(basicConfig, "SimpleOutput", "RecEncoder",
SIMPLE_ENCODER_X264);
config_set_default_bool(basicConfig, "SimpleOutput", "RecRB", false);
config_set_default_int(basicConfig, "SimpleOutput", "RecRBTime", 20);
config_set_default_int(basicConfig, "SimpleOutput", "RecRBSize", 512);
config_set_default_string(basicConfig, "SimpleOutput", "RecRBPrefix",
"Replay");
config_set_default_bool (basicConfig, "AdvOut", "ApplyServiceSettings",
true);
config_set_default_bool (basicConfig, "AdvOut", "UseRescale", false);
config_set_default_uint (basicConfig, "AdvOut", "TrackIndex", 1);
config_set_default_string(basicConfig, "AdvOut", "Encoder", "obs_x264");
config_set_default_string(basicConfig, "AdvOut", "RecType", "Standard");
config_set_default_string(basicConfig, "AdvOut", "RecFilePath",
GetDefaultVideoSavePath().c_str());
config_set_default_string(basicConfig, "AdvOut", "RecFormat", "flv");
config_set_default_bool (basicConfig, "AdvOut", "RecUseRescale",
false);
config_set_default_uint (basicConfig, "AdvOut", "RecTracks", (1<<0));
config_set_default_string(basicConfig, "AdvOut", "RecEncoder",
"none");
config_set_default_bool (basicConfig, "AdvOut", "FFOutputToFile",
true);
config_set_default_string(basicConfig, "AdvOut", "FFFilePath",
GetDefaultVideoSavePath().c_str());
config_set_default_string(basicConfig, "AdvOut", "FFExtension", "mp4");
config_set_default_uint (basicConfig, "AdvOut", "FFVBitrate", 2500);
config_set_default_uint (basicConfig, "AdvOut", "FFVGOPSize", 250);
config_set_default_bool (basicConfig, "AdvOut", "FFUseRescale",
false);
config_set_default_bool (basicConfig, "AdvOut", "FFIgnoreCompat",
false);
config_set_default_uint (basicConfig, "AdvOut", "FFABitrate", 160);
config_set_default_uint (basicConfig, "AdvOut", "FFAudioTrack", 1);
config_set_default_uint (basicConfig, "AdvOut", "Track1Bitrate", 160);
config_set_default_uint (basicConfig, "AdvOut", "Track2Bitrate", 160);
config_set_default_uint (basicConfig, "AdvOut", "Track3Bitrate", 160);
config_set_default_uint (basicConfig, "AdvOut", "Track4Bitrate", 160);
config_set_default_uint (basicConfig, "AdvOut", "Track5Bitrate", 160);
config_set_default_uint (basicConfig, "AdvOut", "Track6Bitrate", 160);
config_set_default_uint (basicConfig, "Video", "BaseCX", cx);
config_set_default_uint (basicConfig, "Video", "BaseCY", cy);
/* don't allow BaseCX/BaseCY to be susceptible to defaults changing */
if (!config_has_user_value(basicConfig, "Video", "BaseCX") ||
!config_has_user_value(basicConfig, "Video", "BaseCY")) {
config_set_uint(basicConfig, "Video", "BaseCX", cx);
config_set_uint(basicConfig, "Video", "BaseCY", cy);
config_save_safe(basicConfig, "tmp", nullptr);
}
config_set_default_string(basicConfig, "Output", "FilenameFormatting",
"%CCYY-%MM-%DD %hh-%mm-%ss");
config_set_default_bool (basicConfig, "Output", "DelayEnable", false);
config_set_default_uint (basicConfig, "Output", "DelaySec", 20);
config_set_default_bool (basicConfig, "Output", "DelayPreserve", true);
config_set_default_bool (basicConfig, "Output", "Reconnect", true);
config_set_default_uint (basicConfig, "Output", "RetryDelay", 10);
config_set_default_uint (basicConfig, "Output", "MaxRetries", 20);
config_set_default_string(basicConfig, "Output", "BindIP", "default");
config_set_default_bool (basicConfig, "Output", "NewSocketLoopEnable",
false);
config_set_default_bool (basicConfig, "Output", "LowLatencyEnable",
false);
int i = 0;
uint32_t scale_cx = cx;
uint32_t scale_cy = cy;
/* use a default scaled resolution that has a pixel count no higher
* than 1280x720 */
while (((scale_cx * scale_cy) > (1280 * 720)) && scaled_vals[i] > 0.0) {
double scale = scaled_vals[i++];
scale_cx = uint32_t(double(cx) / scale);
scale_cy = uint32_t(double(cy) / scale);
}
config_set_default_uint (basicConfig, "Video", "OutputCX", scale_cx);
config_set_default_uint (basicConfig, "Video", "OutputCY", scale_cy);
/* don't allow OutputCX/OutputCY to be susceptible to defaults
* changing */
if (!config_has_user_value(basicConfig, "Video", "OutputCX") ||
!config_has_user_value(basicConfig, "Video", "OutputCY")) {
config_set_uint(basicConfig, "Video", "OutputCX", scale_cx);
config_set_uint(basicConfig, "Video", "OutputCY", scale_cy);
config_save_safe(basicConfig, "tmp", nullptr);
}
config_set_default_uint (basicConfig, "Video", "FPSType", 0);
config_set_default_string(basicConfig, "Video", "FPSCommon", "30");
config_set_default_uint (basicConfig, "Video", "FPSInt", 30);
config_set_default_uint (basicConfig, "Video", "FPSNum", 30);
config_set_default_uint (basicConfig, "Video", "FPSDen", 1);
config_set_default_string(basicConfig, "Video", "ScaleType", "bicubic");
config_set_default_string(basicConfig, "Video", "ColorFormat", "NV12");
config_set_default_string(basicConfig, "Video", "ColorSpace", "601");
config_set_default_string(basicConfig, "Video", "ColorRange",
"Partial");
config_set_default_string(basicConfig, "Audio", "MonitoringDeviceId",
"default");
config_set_default_string(basicConfig, "Audio", "MonitoringDeviceName",
Str("Basic.Settings.Advanced.Audio.MonitoringDevice"
".Default"));
config_set_default_uint (basicConfig, "Audio", "SampleRate", 44100);
config_set_default_string(basicConfig, "Audio", "ChannelSetup",
"Stereo");
return true;
}
獲得視頻保存路徑
const char *path = config_get_string(main->Config(), "SimpleOutput",
"FilePath");
3)保存路徑
void OBSBasicSettings::SaveOutputSettings()
{
config_set_string(main->Config(), "Output", "Mode",
OutputModeFromIdx(ui->outputMode->currentIndex()));
SaveEdit(ui->simpleOutputPath, "SimpleOutput", "FilePath");
void OBSBasicSettings::SaveEdit(QLineEdit *widget, const char *section,
const char *value)
{
if (WidgetChanged(widget))
config_set_string(main->Config(), section, value,
QT_TO_UTF8(widget->text()));
}
三 開始錄製
1) 開始錄製
void OBSBasic::StartRecording()
{
if (outputHandler->RecordingActive())
return;
if (disableOutputsRef)
return;
if (api)
api->on_event(OBS_FRONTEND_EVENT_RECORDING_STARTING);
SaveProject();
outputHandler->StartRecording();
}
bool SimpleOutput::StartRecording()
{
UpdateRecording();
if (!ConfigureRecording(false))
return false;
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;
}
2)錄製的 視頻文件名 和路徑
路徑:
const char *path = config_get_string(main->Config(),
"SimpleOutput", "FilePath");
D:/錄像
const char *filenameFormat = config_get_string(main->Config(), "Output",
"FilenameFormatting");
%CCYY-%MM-%DD %hh-%mm-%ss
bool SimpleOutput::ConfigureRecording(bool updateReplayBuffer)
{
const char *path = config_get_string(main->Config(),
"SimpleOutput", "FilePath");
const char *format = config_get_string(main->Config(),
"SimpleOutput", "RecFormat");
const char *mux = config_get_string(main->Config(), "SimpleOutput",
"MuxerCustom");
bool noSpace = config_get_bool(main->Config(), "SimpleOutput",
"FileNameWithoutSpace");
const char *filenameFormat = config_get_string(main->Config(), "Output",
"FilenameFormatting");
bool overwriteIfExists = config_get_bool(main->Config(), "Output",
"OverwriteIfExists");
const char *rbPrefix = config_get_string(main->Config(), "SimpleOutput",
"RecRBPrefix");
const char *rbSuffix = config_get_string(main->Config(), "SimpleOutput",
"RecRBSuffix");
int rbTime = config_get_int(main->Config(), "SimpleOutput",
"RecRBTime");
int rbSize = config_get_int(main->Config(), "SimpleOutput",
"RecRBSize");
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(ffmpegOutput ? "avi" : format,
noSpace, filenameFormat);
ensure_directory_exists(strPath);
if (!overwriteIfExists)
FindBestFilename(strPath, noSpace);
obs_data_t *settings = obs_data_create();
if (updateReplayBuffer) {
string f;
if (rbPrefix && *rbPrefix) {
f += rbPrefix;
if (f.back() != ' ')
f += " ";
}
f += filenameFormat;
if (rbSuffix && *rbSuffix) {
if (*rbSuffix != ' ')
f += " ";
f += rbSuffix;
}
remove_reserved_file_characters(f);
obs_data_set_string(settings, "directory", path);
obs_data_set_string(settings, "format", f.c_str());
obs_data_set_string(settings, "extension", format);
obs_data_set_int(settings, "max_time_sec", rbTime);
obs_data_set_int(settings, "max_size_mb",
usingRecordingPreset ? rbSize : 0);
} else {
obs_data_set_string(settings, ffmpegOutput ? "url" : "path",
strPath.c_str());
}
obs_data_set_string(settings, "muxer_settings", mux);
if (updateReplayBuffer)
obs_output_update(replayBuffer, settings);
else
obs_output_update(fileOutput, settings);
obs_data_release(settings);
return true;
}
四 停止錄製
開始錄製 停止錄製,都是用的同一個UI按鈕
void OBSBasic::on_recordButton_clicked()
{
if (outputHandler->RecordingActive())
StopRecording();
else
StartRecording();
}
需要注意的是:StopRecording()雖然結束錄製,但此時錄製的視頻文件還沒有完全生成
void OBSBasic::StopRecording()
{
SaveProject();
if (outputHandler->RecordingActive())
outputHandler->StopRecording(recordingStopping);
OnDeactivate();
}
void AdvancedOutput::StopRecording(bool force)
{
if (force)
obs_output_force_stop(fileOutput);
else
obs_output_stop(fileOutput);
}
void obs_output_stop(obs_output_t *output)
{
bool encoded;
if (!obs_output_valid(output, "obs_output_stop"))
return;
if (!output->context.data)
return;
if (!active(output) && !reconnecting(output))
return;
if (reconnecting(output)) {
obs_output_force_stop(output);
return;
}
encoded = (output->info.flags & OBS_OUTPUT_ENCODED) != 0;
if (encoded && output->active_delay_ns) {
obs_output_delay_stop(output);
} else if (!stopping(output)) {
do_output_signal(output, "stopping");
obs_output_actual_stop(output, false, os_gettime_ns());
}
}
static inline void do_output_signal(struct obs_output *output,
const char *signal)
{
struct calldata params = {0};
calldata_set_ptr(¶ms, "output", output);
signal_handler_signal(output->context.signals, signal, ¶ms);
calldata_free(¶ms);
}
相當於發送了一個信號
static const char *output_signals[] = {
"void start(ptr output)",
"void stop(ptr output, int code)",
"void starting(ptr output)",
"void stopping(ptr output)",
"void activate(ptr output)",
"void deactivate(ptr output)",
"void reconnect(ptr output)",
"void reconnect_success(ptr output)",
NULL
};
點擊停止錄製後,按鈕名變爲停止錄製...,實際生成視頻完成時,按鈕再變爲開始錄製
生成完錄製視頻的地方
void OBSBasic::RecordingStop(int code)
{
ui->statusbar->RecordingStopped();
ui->recordButton->setText(QTStr("Basic.Main.StartRecording")); //開始錄製 錄製結束 生成錄製視頻後
ShowUploadDlg(); //生成錄製視頻完成後,再顯示上傳對話框
if (sysTrayRecord)
sysTrayRecord->setText(ui->recordButton->text());
blog(LOG_INFO, RECORDING_STOP);
if (code == OBS_OUTPUT_UNSUPPORTED && isVisible()) {
OBSMessageBox::information(this,
QTStr("Output.RecordFail.Title"),
QTStr("Output.RecordFail.Unsupported"));
} else if (code == OBS_OUTPUT_NO_SPACE && isVisible()) {
OBSMessageBox::information(this,
QTStr("Output.RecordNoSpace.Title"),
QTStr("Output.RecordNoSpace.Msg"));
} else if (code != OBS_OUTPUT_SUCCESS && isVisible()) {
OBSMessageBox::information(this,
QTStr("Output.RecordError.Title"),
QTStr("Output.RecordError.Msg"));
} else if (code == OBS_OUTPUT_UNSUPPORTED && !isVisible()) {
SysTrayNotify(QTStr("Output.RecordFail.Unsupported"),
QSystemTrayIcon::Warning);
} else if (code == OBS_OUTPUT_NO_SPACE && !isVisible()) {
SysTrayNotify(QTStr("Output.RecordNoSpace.Msg"),
QSystemTrayIcon::Warning);
} else if (code != OBS_OUTPUT_SUCCESS && !isVisible()) {
SysTrayNotify(QTStr("Output.RecordError.Msg"),
QSystemTrayIcon::Warning);
}
if (api)
api->on_event(OBS_FRONTEND_EVENT_RECORDING_STOPPED);
OnDeactivate();
}
下面分析下發送信號主要過程
static inline void signal_stop(struct obs_output *output)
{
struct calldata params;
calldata_init(¶ms);
calldata_set_string(¶ms, "last_error", output->last_error_message);
calldata_set_int(¶ms, "code", output->stop_code);
calldata_set_ptr(¶ms, "output", output);
signal_handler_signal(output->context.signals, "stop", ¶ms);
calldata_free(¶ms);
}
static void OBSStopRecording(void *data, calldata_t *params)
{
BasicOutputHandler *output = static_cast<BasicOutputHandler*>(data);
int code = (int)calldata_int(params, "code");
output->recordingActive = false;
QMetaObject::invokeMethod(output->main,
"RecordingStop", Q_ARG(int, code)); //void OBSBasic::RecordingStop(int code)
UNUSED_PARAMETER(params);
}