SimpleOutput.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935
  1. #include "SimpleOutput.hpp"
  2. #include <utility/audio-encoders.hpp>
  3. #include <utility/StartMultiTrackVideoStreamingGuard.hpp>
  4. #include <widgets/OBSBasic.hpp>
  5. #include <qt-wrappers.hpp>
  6. using namespace std;
  7. static bool CreateSimpleAACEncoder(OBSEncoder &res, int bitrate, const char *name, size_t idx)
  8. {
  9. const char *id_ = GetSimpleAACEncoderForBitrate(bitrate);
  10. if (!id_) {
  11. res = nullptr;
  12. return false;
  13. }
  14. res = obs_audio_encoder_create(id_, name, nullptr, idx, nullptr);
  15. if (res) {
  16. obs_encoder_release(res);
  17. return true;
  18. }
  19. return false;
  20. }
  21. static bool CreateSimpleOpusEncoder(OBSEncoder &res, int bitrate, const char *name, size_t idx)
  22. {
  23. const char *id_ = GetSimpleOpusEncoderForBitrate(bitrate);
  24. if (!id_) {
  25. res = nullptr;
  26. return false;
  27. }
  28. res = obs_audio_encoder_create(id_, name, nullptr, idx, nullptr);
  29. if (res) {
  30. obs_encoder_release(res);
  31. return true;
  32. }
  33. return false;
  34. }
  35. extern bool EncoderAvailable(const char *encoder);
  36. void SimpleOutput::LoadRecordingPreset_Lossless()
  37. {
  38. fileOutput = obs_output_create("ffmpeg_output", "simple_ffmpeg_output", nullptr, nullptr);
  39. if (!fileOutput)
  40. throw "Failed to create recording FFmpeg output "
  41. "(simple output)";
  42. OBSDataAutoRelease settings = obs_data_create();
  43. obs_data_set_string(settings, "format_name", "avi");
  44. obs_data_set_string(settings, "video_encoder", "utvideo");
  45. obs_data_set_string(settings, "audio_encoder", "pcm_s16le");
  46. obs_output_update(fileOutput, settings);
  47. }
  48. void SimpleOutput::LoadRecordingPreset_Lossy(const char *encoderId)
  49. {
  50. videoRecording = obs_video_encoder_create(encoderId, "simple_video_recording", nullptr, nullptr);
  51. if (!videoRecording)
  52. throw "Failed to create video recording encoder (simple output)";
  53. obs_encoder_release(videoRecording);
  54. }
  55. void SimpleOutput::LoadStreamingPreset_Lossy(const char *encoderId)
  56. {
  57. videoStreaming = obs_video_encoder_create(encoderId, "simple_video_stream", nullptr, nullptr);
  58. if (!videoStreaming)
  59. throw "Failed to create video streaming encoder (simple output)";
  60. obs_encoder_release(videoStreaming);
  61. if (whipSimulcastEncoders != nullptr) {
  62. whipSimulcastEncoders->Create(encoderId, config_get_int(main->Config(), "AdvOut", "RescaleFilter"),
  63. config_get_int(main->Config(), "Stream1", "WHIPSimulcastTotalLayers"),
  64. video_output_get_width(obs_get_video()),
  65. video_output_get_height(obs_get_video()));
  66. }
  67. }
  68. /* mistakes have been made to lead us to this. */
  69. const char *get_simple_output_encoder(const char *encoder)
  70. {
  71. if (strcmp(encoder, SIMPLE_ENCODER_X264) == 0) {
  72. return "obs_x264";
  73. } else if (strcmp(encoder, SIMPLE_ENCODER_X264_LOWCPU) == 0) {
  74. return "obs_x264";
  75. } else if (strcmp(encoder, SIMPLE_ENCODER_QSV) == 0) {
  76. return "obs_qsv11_v2";
  77. } else if (strcmp(encoder, SIMPLE_ENCODER_QSV_AV1) == 0) {
  78. return "obs_qsv11_av1";
  79. } else if (strcmp(encoder, SIMPLE_ENCODER_AMD) == 0) {
  80. return "h264_texture_amf";
  81. #ifdef ENABLE_HEVC
  82. } else if (strcmp(encoder, SIMPLE_ENCODER_AMD_HEVC) == 0) {
  83. return "h265_texture_amf";
  84. #endif
  85. } else if (strcmp(encoder, SIMPLE_ENCODER_AMD_AV1) == 0) {
  86. return "av1_texture_amf";
  87. } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC) == 0) {
  88. return EncoderAvailable("obs_nvenc_h264_tex") ? "obs_nvenc_h264_tex" : "ffmpeg_nvenc";
  89. #ifdef ENABLE_HEVC
  90. } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC_HEVC) == 0) {
  91. return EncoderAvailable("obs_nvenc_hevc_tex") ? "obs_nvenc_hevc_tex" : "ffmpeg_hevc_nvenc";
  92. #endif
  93. } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC_AV1) == 0) {
  94. return "obs_nvenc_av1_tex";
  95. } else if (strcmp(encoder, SIMPLE_ENCODER_APPLE_H264) == 0) {
  96. return "com.apple.videotoolbox.videoencoder.ave.avc";
  97. #ifdef ENABLE_HEVC
  98. } else if (strcmp(encoder, SIMPLE_ENCODER_APPLE_HEVC) == 0) {
  99. return "com.apple.videotoolbox.videoencoder.ave.hevc";
  100. #endif
  101. }
  102. return "obs_x264";
  103. }
  104. void SimpleOutput::LoadRecordingPreset()
  105. {
  106. const char *quality = config_get_string(main->Config(), "SimpleOutput", "RecQuality");
  107. const char *encoder = config_get_string(main->Config(), "SimpleOutput", "RecEncoder");
  108. const char *audio_encoder = config_get_string(main->Config(), "SimpleOutput", "RecAudioEncoder");
  109. videoEncoder = encoder;
  110. videoQuality = quality;
  111. ffmpegOutput = false;
  112. if (strcmp(quality, "Stream") == 0) {
  113. videoRecording = videoStreaming;
  114. audioRecording = audioStreaming;
  115. usingRecordingPreset = false;
  116. return;
  117. } else if (strcmp(quality, "Lossless") == 0) {
  118. LoadRecordingPreset_Lossless();
  119. usingRecordingPreset = true;
  120. ffmpegOutput = true;
  121. return;
  122. } else {
  123. lowCPUx264 = false;
  124. if (strcmp(encoder, SIMPLE_ENCODER_X264_LOWCPU) == 0)
  125. lowCPUx264 = true;
  126. LoadRecordingPreset_Lossy(get_simple_output_encoder(encoder));
  127. usingRecordingPreset = true;
  128. bool success = false;
  129. if (strcmp(audio_encoder, "opus") == 0)
  130. success = CreateSimpleOpusEncoder(audioRecording, 192, "simple_opus_recording", 0);
  131. else
  132. success = CreateSimpleAACEncoder(audioRecording, 192, "simple_aac_recording", 0);
  133. if (!success)
  134. throw "Failed to create audio recording encoder "
  135. "(simple output)";
  136. for (int i = 0; i < MAX_AUDIO_MIXES; i++) {
  137. char name[23];
  138. if (strcmp(audio_encoder, "opus") == 0) {
  139. snprintf(name, sizeof name, "simple_opus_recording%d", i);
  140. success = CreateSimpleOpusEncoder(audioTrack[i], GetAudioBitrate(), name, i);
  141. } else {
  142. snprintf(name, sizeof name, "simple_aac_recording%d", i);
  143. success = CreateSimpleAACEncoder(audioTrack[i], GetAudioBitrate(), name, i);
  144. }
  145. if (!success)
  146. throw "Failed to create multi-track audio recording encoder "
  147. "(simple output)";
  148. }
  149. }
  150. }
  151. #define SIMPLE_ARCHIVE_NAME "simple_archive_audio"
  152. SimpleOutput::SimpleOutput(OBSBasic *main_) : BasicOutputHandler(main_)
  153. {
  154. const char *encoder = config_get_string(main->Config(), "SimpleOutput", "StreamEncoder");
  155. const char *audio_encoder = config_get_string(main->Config(), "SimpleOutput", "StreamAudioEncoder");
  156. LoadStreamingPreset_Lossy(get_simple_output_encoder(encoder));
  157. bool success = false;
  158. if (strcmp(audio_encoder, "opus") == 0)
  159. success = CreateSimpleOpusEncoder(audioStreaming, GetAudioBitrate(), "simple_opus", 0);
  160. else
  161. success = CreateSimpleAACEncoder(audioStreaming, GetAudioBitrate(), "simple_aac", 0);
  162. if (!success)
  163. throw "Failed to create audio streaming encoder (simple output)";
  164. if (strcmp(audio_encoder, "opus") == 0)
  165. success = CreateSimpleOpusEncoder(audioArchive, GetAudioBitrate(), SIMPLE_ARCHIVE_NAME, 1);
  166. else
  167. success = CreateSimpleAACEncoder(audioArchive, GetAudioBitrate(), SIMPLE_ARCHIVE_NAME, 1);
  168. if (!success)
  169. throw "Failed to create audio archive encoder (simple output)";
  170. LoadRecordingPreset();
  171. if (!ffmpegOutput) {
  172. bool useReplayBuffer = config_get_bool(main->Config(), "SimpleOutput", "RecRB");
  173. const char *recFormat = config_get_string(main->Config(), "SimpleOutput", "RecFormat2");
  174. if (useReplayBuffer) {
  175. OBSDataAutoRelease hotkey;
  176. const char *str = config_get_string(main->Config(), "Hotkeys", "ReplayBuffer");
  177. if (str)
  178. hotkey = obs_data_create_from_json(str);
  179. else
  180. hotkey = nullptr;
  181. replayBuffer = obs_output_create("replay_buffer", Str("ReplayBuffer"), nullptr, hotkey);
  182. if (!replayBuffer)
  183. throw "Failed to create replay buffer output "
  184. "(simple output)";
  185. signal_handler_t *signal = obs_output_get_signal_handler(replayBuffer);
  186. startReplayBuffer.Connect(signal, "start", OBSStartReplayBuffer, this);
  187. stopReplayBuffer.Connect(signal, "stop", OBSStopReplayBuffer, this);
  188. replayBufferStopping.Connect(signal, "stopping", OBSReplayBufferStopping, this);
  189. replayBufferSaved.Connect(signal, "saved", OBSReplayBufferSaved, this);
  190. }
  191. const char *mux = "ffmpeg_muxer";
  192. if (strcmp(recFormat, "hybrid_mp4") == 0)
  193. mux = "mp4_output";
  194. else if (strcmp(recFormat, "hybrid_mov") == 0)
  195. mux = "mov_output";
  196. fileOutput = obs_output_create(mux, "simple_file_output", nullptr, nullptr);
  197. if (!fileOutput)
  198. throw "Failed to create recording output "
  199. "(simple output)";
  200. }
  201. startRecording.Connect(obs_output_get_signal_handler(fileOutput), "start", OBSStartRecording, this);
  202. stopRecording.Connect(obs_output_get_signal_handler(fileOutput), "stop", OBSStopRecording, this);
  203. recordStopping.Connect(obs_output_get_signal_handler(fileOutput), "stopping", OBSRecordStopping, this);
  204. }
  205. int SimpleOutput::GetAudioBitrate() const
  206. {
  207. const char *audio_encoder = config_get_string(main->Config(), "SimpleOutput", "StreamAudioEncoder");
  208. int bitrate = (int)config_get_uint(main->Config(), "SimpleOutput", "ABitrate");
  209. if (strcmp(audio_encoder, "opus") == 0)
  210. return FindClosestAvailableSimpleOpusBitrate(bitrate);
  211. return FindClosestAvailableSimpleAACBitrate(bitrate);
  212. }
  213. void SimpleOutput::Update()
  214. {
  215. OBSDataAutoRelease videoSettings = obs_data_create();
  216. OBSDataAutoRelease audioSettings = obs_data_create();
  217. int videoBitrate = config_get_uint(main->Config(), "SimpleOutput", "VBitrate");
  218. int audioBitrate = GetAudioBitrate();
  219. bool advanced = config_get_bool(main->Config(), "SimpleOutput", "UseAdvanced");
  220. bool enforceBitrate = !config_get_bool(main->Config(), "Stream1", "IgnoreRecommended");
  221. const char *custom = config_get_string(main->Config(), "SimpleOutput", "x264Settings");
  222. const char *encoder = config_get_string(main->Config(), "SimpleOutput", "StreamEncoder");
  223. const char *encoder_id = obs_encoder_get_id(videoStreaming);
  224. const char *presetType;
  225. const char *preset;
  226. if (strcmp(encoder, SIMPLE_ENCODER_QSV) == 0) {
  227. presetType = "QSVPreset";
  228. } else if (strcmp(encoder, SIMPLE_ENCODER_QSV_AV1) == 0) {
  229. presetType = "QSVPreset";
  230. } else if (strcmp(encoder, SIMPLE_ENCODER_AMD) == 0) {
  231. presetType = "AMDPreset";
  232. #ifdef ENABLE_HEVC
  233. } else if (strcmp(encoder, SIMPLE_ENCODER_AMD_HEVC) == 0) {
  234. presetType = "AMDPreset";
  235. #endif
  236. } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC) == 0) {
  237. presetType = "NVENCPreset2";
  238. #ifdef ENABLE_HEVC
  239. } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC_HEVC) == 0) {
  240. presetType = "NVENCPreset2";
  241. #endif
  242. } else if (strcmp(encoder, SIMPLE_ENCODER_AMD_AV1) == 0) {
  243. presetType = "AMDAV1Preset";
  244. } else if (strcmp(encoder, SIMPLE_ENCODER_NVENC_AV1) == 0) {
  245. presetType = "NVENCPreset2";
  246. } else {
  247. presetType = "Preset";
  248. }
  249. preset = config_get_string(main->Config(), "SimpleOutput", presetType);
  250. /* Only use preset2 for legacy/FFmpeg NVENC Encoder. */
  251. if (strncmp(encoder_id, "ffmpeg_", 7) == 0 && strcmp(presetType, "NVENCPreset2") == 0) {
  252. obs_data_set_string(videoSettings, "preset2", preset);
  253. } else {
  254. obs_data_set_string(videoSettings, "preset", preset);
  255. }
  256. obs_data_set_string(videoSettings, "rate_control", "CBR");
  257. obs_data_set_int(videoSettings, "bitrate", videoBitrate);
  258. if (advanced)
  259. obs_data_set_string(videoSettings, "x264opts", custom);
  260. obs_data_set_string(audioSettings, "rate_control", "CBR");
  261. obs_data_set_int(audioSettings, "bitrate", audioBitrate);
  262. obs_service_apply_encoder_settings(main->GetService(), videoSettings, audioSettings);
  263. if (!enforceBitrate) {
  264. int maxVideoBitrate;
  265. int maxAudioBitrate;
  266. obs_service_get_max_bitrate(main->GetService(), &maxVideoBitrate, &maxAudioBitrate);
  267. std::string videoBitrateLogString = maxVideoBitrate > 0 ? std::to_string(maxVideoBitrate) : "None";
  268. std::string audioBitrateLogString = maxAudioBitrate > 0 ? std::to_string(maxAudioBitrate) : "None";
  269. blog(LOG_INFO,
  270. "User is ignoring service bitrate limits.\n"
  271. "Service Recommendations:\n"
  272. "\tvideo bitrate: %s\n"
  273. "\taudio bitrate: %s",
  274. videoBitrateLogString.c_str(), audioBitrateLogString.c_str());
  275. obs_data_set_int(videoSettings, "bitrate", videoBitrate);
  276. obs_data_set_int(audioSettings, "bitrate", audioBitrate);
  277. }
  278. video_t *video = obs_get_video();
  279. enum video_format format = video_output_get_format(video);
  280. switch (format) {
  281. case VIDEO_FORMAT_I420:
  282. case VIDEO_FORMAT_NV12:
  283. case VIDEO_FORMAT_I010:
  284. case VIDEO_FORMAT_P010:
  285. break;
  286. default:
  287. obs_encoder_set_preferred_video_format(videoStreaming, VIDEO_FORMAT_NV12);
  288. if (whipSimulcastEncoders != nullptr) {
  289. whipSimulcastEncoders->SetVideoFormat(VIDEO_FORMAT_NV12);
  290. }
  291. }
  292. obs_encoder_update(videoStreaming, videoSettings);
  293. obs_encoder_update(audioStreaming, audioSettings);
  294. obs_encoder_update(audioArchive, audioSettings);
  295. if (whipSimulcastEncoders != nullptr) {
  296. whipSimulcastEncoders->Update(videoSettings, videoBitrate);
  297. }
  298. }
  299. void SimpleOutput::UpdateRecordingAudioSettings()
  300. {
  301. OBSDataAutoRelease settings = obs_data_create();
  302. obs_data_set_int(settings, "bitrate", 192);
  303. obs_data_set_string(settings, "rate_control", "CBR");
  304. int tracks = config_get_int(main->Config(), "SimpleOutput", "RecTracks");
  305. const char *recFormat = config_get_string(main->Config(), "SimpleOutput", "RecFormat2");
  306. const char *quality = config_get_string(main->Config(), "SimpleOutput", "RecQuality");
  307. bool flv = strcmp(recFormat, "flv") == 0;
  308. if (flv || strcmp(quality, "Stream") == 0) {
  309. obs_encoder_update(audioRecording, settings);
  310. } else {
  311. for (int i = 0; i < MAX_AUDIO_MIXES; i++) {
  312. if ((tracks & (1 << i)) != 0) {
  313. obs_encoder_update(audioTrack[i], settings);
  314. }
  315. }
  316. }
  317. }
  318. #define CROSS_DIST_CUTOFF 2000.0
  319. int SimpleOutput::CalcCRF(int crf)
  320. {
  321. int cx = config_get_uint(main->Config(), "Video", "OutputCX");
  322. int cy = config_get_uint(main->Config(), "Video", "OutputCY");
  323. double fCX = double(cx);
  324. double fCY = double(cy);
  325. if (lowCPUx264)
  326. crf -= 2;
  327. double crossDist = sqrt(fCX * fCX + fCY * fCY);
  328. double crfResReduction = fmin(CROSS_DIST_CUTOFF, crossDist) / CROSS_DIST_CUTOFF;
  329. crfResReduction = (1.0 - crfResReduction) * 10.0;
  330. return crf - int(crfResReduction);
  331. }
  332. void SimpleOutput::UpdateRecordingSettings_x264_crf(int crf)
  333. {
  334. OBSDataAutoRelease settings = obs_data_create();
  335. obs_data_set_int(settings, "crf", crf);
  336. obs_data_set_bool(settings, "use_bufsize", true);
  337. obs_data_set_string(settings, "rate_control", "CRF");
  338. obs_data_set_string(settings, "profile", "high");
  339. obs_data_set_string(settings, "preset", lowCPUx264 ? "ultrafast" : "veryfast");
  340. obs_encoder_update(videoRecording, settings);
  341. }
  342. static bool icq_available(obs_encoder_t *encoder)
  343. {
  344. obs_properties_t *props = obs_encoder_properties(encoder);
  345. obs_property_t *p = obs_properties_get(props, "rate_control");
  346. bool icq_found = false;
  347. size_t num = obs_property_list_item_count(p);
  348. for (size_t i = 0; i < num; i++) {
  349. const char *val = obs_property_list_item_string(p, i);
  350. if (strcmp(val, "ICQ") == 0) {
  351. icq_found = true;
  352. break;
  353. }
  354. }
  355. obs_properties_destroy(props);
  356. return icq_found;
  357. }
  358. void SimpleOutput::UpdateRecordingSettings_qsv11(int crf, bool av1)
  359. {
  360. bool icq = icq_available(videoRecording);
  361. OBSDataAutoRelease settings = obs_data_create();
  362. obs_data_set_string(settings, "profile", "high");
  363. if (icq && !av1) {
  364. obs_data_set_string(settings, "rate_control", "ICQ");
  365. obs_data_set_int(settings, "icq_quality", crf);
  366. } else {
  367. obs_data_set_string(settings, "rate_control", "CQP");
  368. obs_data_set_int(settings, "cqp", crf);
  369. }
  370. obs_encoder_update(videoRecording, settings);
  371. }
  372. void SimpleOutput::UpdateRecordingSettings_nvenc(int cqp)
  373. {
  374. OBSDataAutoRelease settings = obs_data_create();
  375. obs_data_set_string(settings, "rate_control", "CQP");
  376. obs_data_set_string(settings, "profile", "high");
  377. obs_data_set_int(settings, "cqp", cqp);
  378. obs_encoder_update(videoRecording, settings);
  379. }
  380. void SimpleOutput::UpdateRecordingSettings_nvenc_hevc_av1(int cqp)
  381. {
  382. OBSDataAutoRelease settings = obs_data_create();
  383. obs_data_set_string(settings, "rate_control", "CQP");
  384. obs_data_set_string(settings, "profile", "main");
  385. obs_data_set_int(settings, "cqp", cqp);
  386. obs_encoder_update(videoRecording, settings);
  387. }
  388. void SimpleOutput::UpdateRecordingSettings_apple(int quality)
  389. {
  390. OBSDataAutoRelease settings = obs_data_create();
  391. obs_data_set_string(settings, "rate_control", "CRF");
  392. obs_data_set_string(settings, "profile", "high");
  393. obs_data_set_int(settings, "quality", quality);
  394. obs_encoder_update(videoRecording, settings);
  395. }
  396. #ifdef ENABLE_HEVC
  397. void SimpleOutput::UpdateRecordingSettings_apple_hevc(int quality)
  398. {
  399. OBSDataAutoRelease settings = obs_data_create();
  400. obs_data_set_string(settings, "rate_control", "CRF");
  401. obs_data_set_string(settings, "profile", "main");
  402. obs_data_set_int(settings, "quality", quality);
  403. obs_encoder_update(videoRecording, settings);
  404. }
  405. #endif
  406. void SimpleOutput::UpdateRecordingSettings_amd_cqp(int cqp)
  407. {
  408. OBSDataAutoRelease settings = obs_data_create();
  409. obs_data_set_string(settings, "rate_control", "CQP");
  410. obs_data_set_string(settings, "profile", "high");
  411. obs_data_set_string(settings, "preset", "quality");
  412. obs_data_set_int(settings, "cqp", cqp);
  413. obs_encoder_update(videoRecording, settings);
  414. }
  415. void SimpleOutput::UpdateRecordingSettings()
  416. {
  417. bool ultra_hq = (videoQuality == "HQ");
  418. int crf = CalcCRF(ultra_hq ? 16 : 23);
  419. if (astrcmp_n(videoEncoder.c_str(), "x264", 4) == 0) {
  420. UpdateRecordingSettings_x264_crf(crf);
  421. } else if (videoEncoder == SIMPLE_ENCODER_QSV) {
  422. UpdateRecordingSettings_qsv11(crf, false);
  423. } else if (videoEncoder == SIMPLE_ENCODER_QSV_AV1) {
  424. UpdateRecordingSettings_qsv11(crf, true);
  425. } else if (videoEncoder == SIMPLE_ENCODER_AMD) {
  426. UpdateRecordingSettings_amd_cqp(crf);
  427. #ifdef ENABLE_HEVC
  428. } else if (videoEncoder == SIMPLE_ENCODER_AMD_HEVC) {
  429. UpdateRecordingSettings_amd_cqp(crf);
  430. #endif
  431. } else if (videoEncoder == SIMPLE_ENCODER_AMD_AV1) {
  432. UpdateRecordingSettings_amd_cqp(crf);
  433. } else if (videoEncoder == SIMPLE_ENCODER_NVENC) {
  434. UpdateRecordingSettings_nvenc(crf);
  435. #ifdef ENABLE_HEVC
  436. } else if (videoEncoder == SIMPLE_ENCODER_NVENC_HEVC) {
  437. UpdateRecordingSettings_nvenc_hevc_av1(crf);
  438. #endif
  439. } else if (videoEncoder == SIMPLE_ENCODER_NVENC_AV1) {
  440. UpdateRecordingSettings_nvenc_hevc_av1(crf);
  441. } else if (videoEncoder == SIMPLE_ENCODER_APPLE_H264) {
  442. /* These are magic numbers. 0 - 100, more is better. */
  443. UpdateRecordingSettings_apple(ultra_hq ? 70 : 50);
  444. #ifdef ENABLE_HEVC
  445. } else if (videoEncoder == SIMPLE_ENCODER_APPLE_HEVC) {
  446. UpdateRecordingSettings_apple_hevc(ultra_hq ? 70 : 50);
  447. #endif
  448. }
  449. UpdateRecordingAudioSettings();
  450. }
  451. inline void SimpleOutput::SetupOutputs()
  452. {
  453. SimpleOutput::Update();
  454. obs_encoder_set_video(videoStreaming, obs_get_video());
  455. obs_encoder_set_audio(audioStreaming, obs_get_audio());
  456. obs_encoder_set_audio(audioArchive, obs_get_audio());
  457. int tracks = config_get_int(main->Config(), "SimpleOutput", "RecTracks");
  458. const char *recFormat = config_get_string(main->Config(), "SimpleOutput", "RecFormat2");
  459. bool flv = strcmp(recFormat, "flv") == 0;
  460. if (usingRecordingPreset) {
  461. if (ffmpegOutput) {
  462. obs_output_set_media(fileOutput, obs_get_video(), obs_get_audio());
  463. } else {
  464. obs_encoder_set_video(videoRecording, obs_get_video());
  465. if (flv) {
  466. obs_encoder_set_audio(audioRecording, obs_get_audio());
  467. } else {
  468. for (int i = 0; i < MAX_AUDIO_MIXES; i++) {
  469. if ((tracks & (1 << i)) != 0) {
  470. obs_encoder_set_audio(audioTrack[i], obs_get_audio());
  471. }
  472. }
  473. }
  474. }
  475. } else {
  476. obs_encoder_set_audio(audioRecording, obs_get_audio());
  477. }
  478. }
  479. std::shared_future<void> SimpleOutput::SetupStreaming(obs_service_t *service, SetupStreamingContinuation_t continuation)
  480. {
  481. if (!Active())
  482. SetupOutputs();
  483. Auth *auth = main->GetAuth();
  484. if (auth)
  485. auth->OnStreamConfig();
  486. /* --------------------- */
  487. const char *type = GetStreamOutputType(service);
  488. if (!type) {
  489. continuation(false);
  490. return StartMultitrackVideoStreamingGuard::MakeReadyFuture();
  491. }
  492. auto audio_bitrate = GetAudioBitrate();
  493. auto vod_track_mixer = IsVodTrackEnabled(service) ? std::optional{1} : std::nullopt;
  494. auto handle_multitrack_video_result = [=](std::optional<bool> multitrackVideoResult) {
  495. if (multitrackVideoResult.has_value())
  496. return multitrackVideoResult.value();
  497. /* XXX: this is messy and disgusting and should be refactored */
  498. if (outputType != type) {
  499. streamDelayStarting.Disconnect();
  500. streamStopping.Disconnect();
  501. startStreaming.Disconnect();
  502. stopStreaming.Disconnect();
  503. streamOutput = obs_output_create(type, "simple_stream", nullptr, nullptr);
  504. if (!streamOutput) {
  505. blog(LOG_WARNING,
  506. "Creation of stream output type '%s' "
  507. "failed!",
  508. type);
  509. return false;
  510. }
  511. streamDelayStarting.Connect(obs_output_get_signal_handler(streamOutput), "starting",
  512. OBSStreamStarting, this);
  513. streamStopping.Connect(obs_output_get_signal_handler(streamOutput), "stopping",
  514. OBSStreamStopping, this);
  515. startStreaming.Connect(obs_output_get_signal_handler(streamOutput), "start", OBSStartStreaming,
  516. this);
  517. stopStreaming.Connect(obs_output_get_signal_handler(streamOutput), "stop", OBSStopStreaming,
  518. this);
  519. outputType = type;
  520. }
  521. obs_output_set_video_encoder(streamOutput, videoStreaming);
  522. if (whipSimulcastEncoders != nullptr) {
  523. whipSimulcastEncoders->SetStreamOutput(streamOutput);
  524. }
  525. obs_output_set_audio_encoder(streamOutput, audioStreaming, 0);
  526. obs_output_set_service(streamOutput, service);
  527. return true;
  528. };
  529. return SetupMultitrackVideo(service, GetSimpleAACEncoderForBitrate(audio_bitrate), 0, vod_track_mixer,
  530. [=](std::optional<bool> res) {
  531. continuation(handle_multitrack_video_result(res));
  532. });
  533. }
  534. bool SimpleOutput::IsVodTrackEnabled(obs_service_t *service)
  535. {
  536. bool advanced = config_get_bool(main->Config(), "SimpleOutput", "UseAdvanced");
  537. bool enable = config_get_bool(main->Config(), "SimpleOutput", "VodTrackEnabled");
  538. bool enableForCustomServer = config_get_bool(App()->GetUserConfig(), "General", "EnableCustomServerVodTrack");
  539. OBSDataAutoRelease settings = obs_service_get_settings(service);
  540. const char *name = obs_data_get_string(settings, "service");
  541. const char *id = obs_service_get_id(service);
  542. if (strcmp(id, "rtmp_custom") == 0)
  543. return enableForCustomServer ? enable : false;
  544. else
  545. return advanced && enable && ServiceSupportsVodTrack(name);
  546. }
  547. void SimpleOutput::SetupVodTrack(obs_service_t *service)
  548. {
  549. if (IsVodTrackEnabled(service))
  550. obs_output_set_audio_encoder(streamOutput, audioArchive, 1);
  551. else
  552. clear_archive_encoder(streamOutput, SIMPLE_ARCHIVE_NAME);
  553. }
  554. bool SimpleOutput::StartStreaming(obs_service_t *service)
  555. {
  556. bool reconnect = config_get_bool(main->Config(), "Output", "Reconnect");
  557. int retryDelay = config_get_uint(main->Config(), "Output", "RetryDelay");
  558. int maxRetries = config_get_uint(main->Config(), "Output", "MaxRetries");
  559. bool useDelay = config_get_bool(main->Config(), "Output", "DelayEnable");
  560. int delaySec = config_get_int(main->Config(), "Output", "DelaySec");
  561. bool preserveDelay = config_get_bool(main->Config(), "Output", "DelayPreserve");
  562. const char *bindIP = config_get_string(main->Config(), "Output", "BindIP");
  563. const char *ipFamily = config_get_string(main->Config(), "Output", "IPFamily");
  564. #ifdef _WIN32
  565. bool enableNewSocketLoop = config_get_bool(main->Config(), "Output", "NewSocketLoopEnable");
  566. bool enableLowLatencyMode = config_get_bool(main->Config(), "Output", "LowLatencyEnable");
  567. #endif
  568. bool enableDynBitrate = config_get_bool(main->Config(), "Output", "DynamicBitrate");
  569. if (multitrackVideo && multitrackVideoActive &&
  570. !multitrackVideo->HandleIncompatibleSettings(main, main->Config(), service, enableDynBitrate)) {
  571. multitrackVideoActive = false;
  572. return false;
  573. }
  574. OBSDataAutoRelease settings = obs_data_create();
  575. obs_data_set_string(settings, "bind_ip", bindIP);
  576. obs_data_set_string(settings, "ip_family", ipFamily);
  577. #ifdef _WIN32
  578. obs_data_set_bool(settings, "new_socket_loop_enabled", enableNewSocketLoop);
  579. obs_data_set_bool(settings, "low_latency_mode_enabled", enableLowLatencyMode);
  580. #endif
  581. obs_data_set_bool(settings, "dyn_bitrate", enableDynBitrate);
  582. auto streamOutput = StreamingOutput(); // shadowing is sort of bad, but also convenient
  583. obs_output_update(streamOutput, settings);
  584. if (!reconnect)
  585. maxRetries = 0;
  586. obs_output_set_delay(streamOutput, useDelay ? delaySec : 0, preserveDelay ? OBS_OUTPUT_DELAY_PRESERVE : 0);
  587. obs_output_set_reconnect_settings(streamOutput, maxRetries, retryDelay);
  588. if (!multitrackVideo || !multitrackVideoActive)
  589. SetupVodTrack(service);
  590. if (obs_output_start(streamOutput)) {
  591. if (multitrackVideo && multitrackVideoActive)
  592. multitrackVideo->StartedStreaming();
  593. return true;
  594. }
  595. if (multitrackVideo && multitrackVideoActive)
  596. multitrackVideoActive = false;
  597. const char *error = obs_output_get_last_error(streamOutput);
  598. bool hasLastError = error && *error;
  599. if (hasLastError)
  600. lastError = error;
  601. else
  602. lastError = string();
  603. const char *type = obs_output_get_id(streamOutput);
  604. blog(LOG_WARNING, "Stream output type '%s' failed to start!%s%s", type, hasLastError ? " Last Error: " : "",
  605. hasLastError ? error : "");
  606. return false;
  607. }
  608. void SimpleOutput::UpdateRecording()
  609. {
  610. const char *recFormat = config_get_string(main->Config(), "SimpleOutput", "RecFormat2");
  611. bool flv = strcmp(recFormat, "flv") == 0;
  612. int tracks = config_get_int(main->Config(), "SimpleOutput", "RecTracks");
  613. int idx = 0;
  614. int idx2 = 0;
  615. const char *quality = config_get_string(main->Config(), "SimpleOutput", "RecQuality");
  616. if (replayBufferActive || recordingActive)
  617. return;
  618. if (usingRecordingPreset) {
  619. if (!ffmpegOutput)
  620. UpdateRecordingSettings();
  621. } else if (!obs_output_active(streamOutput)) {
  622. Update();
  623. }
  624. if (!Active())
  625. SetupOutputs();
  626. if (!ffmpegOutput) {
  627. obs_output_set_video_encoder(fileOutput, videoRecording);
  628. if (flv || strcmp(quality, "Stream") == 0) {
  629. obs_output_set_audio_encoder(fileOutput, audioRecording, 0);
  630. } else {
  631. for (int i = 0; i < MAX_AUDIO_MIXES; i++) {
  632. if ((tracks & (1 << i)) != 0) {
  633. obs_output_set_audio_encoder(fileOutput, audioTrack[i], idx++);
  634. }
  635. }
  636. }
  637. }
  638. if (replayBuffer) {
  639. obs_output_set_video_encoder(replayBuffer, videoRecording);
  640. if (flv || strcmp(quality, "Stream") == 0) {
  641. obs_output_set_audio_encoder(replayBuffer, audioRecording, 0);
  642. } else {
  643. for (int i = 0; i < MAX_AUDIO_MIXES; i++) {
  644. if ((tracks & (1 << i)) != 0) {
  645. obs_output_set_audio_encoder(replayBuffer, audioTrack[i], idx2++);
  646. }
  647. }
  648. }
  649. }
  650. recordingConfigured = true;
  651. }
  652. bool SimpleOutput::ConfigureRecording(bool updateReplayBuffer)
  653. {
  654. const char *path = config_get_string(main->Config(), "SimpleOutput", "FilePath");
  655. const char *format = config_get_string(main->Config(), "SimpleOutput", "RecFormat2");
  656. const char *mux = config_get_string(main->Config(), "SimpleOutput", "MuxerCustom");
  657. bool noSpace = config_get_bool(main->Config(), "SimpleOutput", "FileNameWithoutSpace");
  658. const char *filenameFormat = config_get_string(main->Config(), "Output", "FilenameFormatting");
  659. bool overwriteIfExists = config_get_bool(main->Config(), "Output", "OverwriteIfExists");
  660. const char *rbPrefix = config_get_string(main->Config(), "SimpleOutput", "RecRBPrefix");
  661. const char *rbSuffix = config_get_string(main->Config(), "SimpleOutput", "RecRBSuffix");
  662. int rbTime = config_get_int(main->Config(), "SimpleOutput", "RecRBTime");
  663. int rbSize = config_get_int(main->Config(), "SimpleOutput", "RecRBSize");
  664. int tracks = config_get_int(main->Config(), "SimpleOutput", "RecTracks");
  665. bool is_fragmented = strncmp(format, "fragmented", 10) == 0;
  666. bool is_lossless = videoQuality == "Lossless";
  667. string f;
  668. OBSDataAutoRelease settings = obs_data_create();
  669. if (updateReplayBuffer) {
  670. f = GetFormatString(filenameFormat, rbPrefix, rbSuffix);
  671. string ext = GetFormatExt(format);
  672. obs_data_set_string(settings, "directory", path);
  673. obs_data_set_string(settings, "format", f.c_str());
  674. obs_data_set_string(settings, "extension", ext.c_str());
  675. obs_data_set_bool(settings, "allow_spaces", !noSpace);
  676. obs_data_set_int(settings, "max_time_sec", rbTime);
  677. obs_data_set_int(settings, "max_size_mb", usingRecordingPreset ? rbSize : 0);
  678. } else {
  679. f = GetFormatString(filenameFormat, nullptr, nullptr);
  680. string strPath = GetRecordingFilename(path, ffmpegOutput ? "avi" : format, noSpace, overwriteIfExists,
  681. f.c_str(), ffmpegOutput);
  682. obs_data_set_string(settings, ffmpegOutput ? "url" : "path", strPath.c_str());
  683. if (ffmpegOutput)
  684. obs_output_set_mixers(fileOutput, tracks);
  685. }
  686. // Use fragmented MOV/MP4 if user has not already specified custom movflags
  687. if (is_fragmented && !is_lossless && (!mux || strstr(mux, "movflags") == NULL)) {
  688. string mux_frag = "movflags=frag_keyframe+empty_moov+delay_moov";
  689. if (mux) {
  690. mux_frag += " ";
  691. mux_frag += mux;
  692. }
  693. obs_data_set_string(settings, "muxer_settings", mux_frag.c_str());
  694. } else {
  695. if (is_fragmented && !is_lossless)
  696. blog(LOG_WARNING, "User enabled fragmented recording, "
  697. "but custom muxer settings contained movflags.");
  698. obs_data_set_string(settings, "muxer_settings", mux);
  699. }
  700. if (updateReplayBuffer)
  701. obs_output_update(replayBuffer, settings);
  702. else
  703. obs_output_update(fileOutput, settings);
  704. return true;
  705. }
  706. bool SimpleOutput::StartRecording()
  707. {
  708. UpdateRecording();
  709. if (!ConfigureRecording(false))
  710. return false;
  711. if (!obs_output_start(fileOutput)) {
  712. QString error_reason;
  713. const char *error = obs_output_get_last_error(fileOutput);
  714. if (error)
  715. error_reason = QT_UTF8(error);
  716. else
  717. error_reason = QTStr("Output.StartFailedGeneric");
  718. QMessageBox::critical(main, QTStr("Output.StartRecordingFailed"), error_reason);
  719. return false;
  720. }
  721. return true;
  722. }
  723. bool SimpleOutput::StartReplayBuffer()
  724. {
  725. UpdateRecording();
  726. if (!ConfigureRecording(true))
  727. return false;
  728. if (!obs_output_start(replayBuffer)) {
  729. QMessageBox::critical(main, QTStr("Output.StartReplayFailed"), QTStr("Output.StartFailedGeneric"));
  730. return false;
  731. }
  732. return true;
  733. }
  734. void SimpleOutput::StopStreaming(bool force)
  735. {
  736. auto output = StreamingOutput();
  737. if (force && output)
  738. obs_output_force_stop(output);
  739. else if (multitrackVideo && multitrackVideoActive)
  740. multitrackVideo->StopStreaming();
  741. else
  742. obs_output_stop(output);
  743. }
  744. void SimpleOutput::StopRecording(bool force)
  745. {
  746. if (force)
  747. obs_output_force_stop(fileOutput);
  748. else
  749. obs_output_stop(fileOutput);
  750. }
  751. void SimpleOutput::StopReplayBuffer(bool force)
  752. {
  753. if (force)
  754. obs_output_force_stop(replayBuffer);
  755. else
  756. obs_output_stop(replayBuffer);
  757. }
  758. bool SimpleOutput::StreamingActive() const
  759. {
  760. return obs_output_active(StreamingOutput());
  761. }
  762. bool SimpleOutput::RecordingActive() const
  763. {
  764. return obs_output_active(fileOutput);
  765. }
  766. bool SimpleOutput::ReplayBufferActive() const
  767. {
  768. return obs_output_active(replayBuffer);
  769. }