SimpleOutput.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933
  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 = [this, type = std::string{type},
  495. service](std::optional<bool> multitrackVideoResult) {
  496. if (multitrackVideoResult.has_value())
  497. return multitrackVideoResult.value();
  498. /* XXX: this is messy and disgusting and should be refactored */
  499. if (outputType != type) {
  500. streamDelayStarting.Disconnect();
  501. streamStopping.Disconnect();
  502. startStreaming.Disconnect();
  503. stopStreaming.Disconnect();
  504. streamOutput = obs_output_create(type.c_str(), "simple_stream", nullptr, nullptr);
  505. if (!streamOutput) {
  506. blog(LOG_WARNING, "Creation of stream output type '%s' failed!", type.c_str());
  507. return false;
  508. }
  509. streamDelayStarting.Connect(obs_output_get_signal_handler(streamOutput), "starting",
  510. OBSStreamStarting, this);
  511. streamStopping.Connect(obs_output_get_signal_handler(streamOutput), "stopping",
  512. OBSStreamStopping, this);
  513. startStreaming.Connect(obs_output_get_signal_handler(streamOutput), "start", OBSStartStreaming,
  514. this);
  515. stopStreaming.Connect(obs_output_get_signal_handler(streamOutput), "stop", OBSStopStreaming,
  516. this);
  517. outputType = type;
  518. }
  519. obs_output_set_video_encoder(streamOutput, videoStreaming);
  520. if (whipSimulcastEncoders != nullptr) {
  521. whipSimulcastEncoders->SetStreamOutput(streamOutput);
  522. }
  523. obs_output_set_audio_encoder(streamOutput, audioStreaming, 0);
  524. obs_output_set_service(streamOutput, service);
  525. return true;
  526. };
  527. return SetupMultitrackVideo(service, GetSimpleAACEncoderForBitrate(audio_bitrate), 0, vod_track_mixer,
  528. [=](std::optional<bool> res) {
  529. continuation(handle_multitrack_video_result(res));
  530. });
  531. }
  532. bool SimpleOutput::IsVodTrackEnabled(obs_service_t *service)
  533. {
  534. bool advanced = config_get_bool(main->Config(), "SimpleOutput", "UseAdvanced");
  535. bool enable = config_get_bool(main->Config(), "SimpleOutput", "VodTrackEnabled");
  536. bool enableForCustomServer = config_get_bool(App()->GetUserConfig(), "General", "EnableCustomServerVodTrack");
  537. OBSDataAutoRelease settings = obs_service_get_settings(service);
  538. const char *name = obs_data_get_string(settings, "service");
  539. const char *id = obs_service_get_id(service);
  540. if (strcmp(id, "rtmp_custom") == 0)
  541. return enableForCustomServer ? enable : false;
  542. else
  543. return advanced && enable && ServiceSupportsVodTrack(name);
  544. }
  545. void SimpleOutput::SetupVodTrack(obs_service_t *service)
  546. {
  547. if (IsVodTrackEnabled(service))
  548. obs_output_set_audio_encoder(streamOutput, audioArchive, 1);
  549. else
  550. clear_archive_encoder(streamOutput, SIMPLE_ARCHIVE_NAME);
  551. }
  552. bool SimpleOutput::StartStreaming(obs_service_t *service)
  553. {
  554. bool reconnect = config_get_bool(main->Config(), "Output", "Reconnect");
  555. int retryDelay = config_get_uint(main->Config(), "Output", "RetryDelay");
  556. int maxRetries = config_get_uint(main->Config(), "Output", "MaxRetries");
  557. bool useDelay = config_get_bool(main->Config(), "Output", "DelayEnable");
  558. int delaySec = config_get_int(main->Config(), "Output", "DelaySec");
  559. bool preserveDelay = config_get_bool(main->Config(), "Output", "DelayPreserve");
  560. const char *bindIP = config_get_string(main->Config(), "Output", "BindIP");
  561. const char *ipFamily = config_get_string(main->Config(), "Output", "IPFamily");
  562. #ifdef _WIN32
  563. bool enableNewSocketLoop = config_get_bool(main->Config(), "Output", "NewSocketLoopEnable");
  564. bool enableLowLatencyMode = config_get_bool(main->Config(), "Output", "LowLatencyEnable");
  565. #endif
  566. bool enableDynBitrate = config_get_bool(main->Config(), "Output", "DynamicBitrate");
  567. if (multitrackVideo && multitrackVideoActive &&
  568. !multitrackVideo->HandleIncompatibleSettings(main, main->Config(), service, enableDynBitrate)) {
  569. multitrackVideoActive = false;
  570. return false;
  571. }
  572. OBSDataAutoRelease settings = obs_data_create();
  573. obs_data_set_string(settings, "bind_ip", bindIP);
  574. obs_data_set_string(settings, "ip_family", ipFamily);
  575. #ifdef _WIN32
  576. obs_data_set_bool(settings, "new_socket_loop_enabled", enableNewSocketLoop);
  577. obs_data_set_bool(settings, "low_latency_mode_enabled", enableLowLatencyMode);
  578. #endif
  579. obs_data_set_bool(settings, "dyn_bitrate", enableDynBitrate);
  580. auto streamOutput = StreamingOutput(); // shadowing is sort of bad, but also convenient
  581. obs_output_update(streamOutput, settings);
  582. if (!reconnect)
  583. maxRetries = 0;
  584. obs_output_set_delay(streamOutput, useDelay ? delaySec : 0, preserveDelay ? OBS_OUTPUT_DELAY_PRESERVE : 0);
  585. obs_output_set_reconnect_settings(streamOutput, maxRetries, retryDelay);
  586. if (!multitrackVideo || !multitrackVideoActive)
  587. SetupVodTrack(service);
  588. if (obs_output_start(streamOutput)) {
  589. if (multitrackVideo && multitrackVideoActive)
  590. multitrackVideo->StartedStreaming();
  591. return true;
  592. }
  593. if (multitrackVideo && multitrackVideoActive)
  594. multitrackVideoActive = false;
  595. const char *error = obs_output_get_last_error(streamOutput);
  596. bool hasLastError = error && *error;
  597. if (hasLastError)
  598. lastError = error;
  599. else
  600. lastError = string();
  601. const char *type = obs_output_get_id(streamOutput);
  602. blog(LOG_WARNING, "Stream output type '%s' failed to start!%s%s", type, hasLastError ? " Last Error: " : "",
  603. hasLastError ? error : "");
  604. return false;
  605. }
  606. void SimpleOutput::UpdateRecording()
  607. {
  608. const char *recFormat = config_get_string(main->Config(), "SimpleOutput", "RecFormat2");
  609. bool flv = strcmp(recFormat, "flv") == 0;
  610. int tracks = config_get_int(main->Config(), "SimpleOutput", "RecTracks");
  611. int idx = 0;
  612. int idx2 = 0;
  613. const char *quality = config_get_string(main->Config(), "SimpleOutput", "RecQuality");
  614. if (replayBufferActive || recordingActive)
  615. return;
  616. if (usingRecordingPreset) {
  617. if (!ffmpegOutput)
  618. UpdateRecordingSettings();
  619. } else if (!obs_output_active(streamOutput)) {
  620. Update();
  621. }
  622. if (!Active())
  623. SetupOutputs();
  624. if (!ffmpegOutput) {
  625. obs_output_set_video_encoder(fileOutput, videoRecording);
  626. if (flv || strcmp(quality, "Stream") == 0) {
  627. obs_output_set_audio_encoder(fileOutput, audioRecording, 0);
  628. } else {
  629. for (int i = 0; i < MAX_AUDIO_MIXES; i++) {
  630. if ((tracks & (1 << i)) != 0) {
  631. obs_output_set_audio_encoder(fileOutput, audioTrack[i], idx++);
  632. }
  633. }
  634. }
  635. }
  636. if (replayBuffer) {
  637. obs_output_set_video_encoder(replayBuffer, videoRecording);
  638. if (flv || strcmp(quality, "Stream") == 0) {
  639. obs_output_set_audio_encoder(replayBuffer, audioRecording, 0);
  640. } else {
  641. for (int i = 0; i < MAX_AUDIO_MIXES; i++) {
  642. if ((tracks & (1 << i)) != 0) {
  643. obs_output_set_audio_encoder(replayBuffer, audioTrack[i], idx2++);
  644. }
  645. }
  646. }
  647. }
  648. recordingConfigured = true;
  649. }
  650. bool SimpleOutput::ConfigureRecording(bool updateReplayBuffer)
  651. {
  652. const char *path = config_get_string(main->Config(), "SimpleOutput", "FilePath");
  653. const char *format = config_get_string(main->Config(), "SimpleOutput", "RecFormat2");
  654. const char *mux = config_get_string(main->Config(), "SimpleOutput", "MuxerCustom");
  655. bool noSpace = config_get_bool(main->Config(), "SimpleOutput", "FileNameWithoutSpace");
  656. const char *filenameFormat = config_get_string(main->Config(), "Output", "FilenameFormatting");
  657. bool overwriteIfExists = config_get_bool(main->Config(), "Output", "OverwriteIfExists");
  658. const char *rbPrefix = config_get_string(main->Config(), "SimpleOutput", "RecRBPrefix");
  659. const char *rbSuffix = config_get_string(main->Config(), "SimpleOutput", "RecRBSuffix");
  660. int rbTime = config_get_int(main->Config(), "SimpleOutput", "RecRBTime");
  661. int rbSize = config_get_int(main->Config(), "SimpleOutput", "RecRBSize");
  662. int tracks = config_get_int(main->Config(), "SimpleOutput", "RecTracks");
  663. bool is_fragmented = strncmp(format, "fragmented", 10) == 0;
  664. bool is_lossless = videoQuality == "Lossless";
  665. string f;
  666. OBSDataAutoRelease settings = obs_data_create();
  667. if (updateReplayBuffer) {
  668. f = GetFormatString(filenameFormat, rbPrefix, rbSuffix);
  669. string ext = GetFormatExt(format);
  670. obs_data_set_string(settings, "directory", path);
  671. obs_data_set_string(settings, "format", f.c_str());
  672. obs_data_set_string(settings, "extension", ext.c_str());
  673. obs_data_set_bool(settings, "allow_spaces", !noSpace);
  674. obs_data_set_int(settings, "max_time_sec", rbTime);
  675. obs_data_set_int(settings, "max_size_mb", usingRecordingPreset ? rbSize : 0);
  676. } else {
  677. f = GetFormatString(filenameFormat, nullptr, nullptr);
  678. string strPath = GetRecordingFilename(path, ffmpegOutput ? "avi" : format, noSpace, overwriteIfExists,
  679. f.c_str(), ffmpegOutput);
  680. obs_data_set_string(settings, ffmpegOutput ? "url" : "path", strPath.c_str());
  681. if (ffmpegOutput)
  682. obs_output_set_mixers(fileOutput, tracks);
  683. }
  684. // Use fragmented MOV/MP4 if user has not already specified custom movflags
  685. if (is_fragmented && !is_lossless && (!mux || strstr(mux, "movflags") == NULL)) {
  686. string mux_frag = "movflags=frag_keyframe+empty_moov+delay_moov";
  687. if (mux) {
  688. mux_frag += " ";
  689. mux_frag += mux;
  690. }
  691. obs_data_set_string(settings, "muxer_settings", mux_frag.c_str());
  692. } else {
  693. if (is_fragmented && !is_lossless)
  694. blog(LOG_WARNING, "User enabled fragmented recording, "
  695. "but custom muxer settings contained movflags.");
  696. obs_data_set_string(settings, "muxer_settings", mux);
  697. }
  698. if (updateReplayBuffer)
  699. obs_output_update(replayBuffer, settings);
  700. else
  701. obs_output_update(fileOutput, settings);
  702. return true;
  703. }
  704. bool SimpleOutput::StartRecording()
  705. {
  706. UpdateRecording();
  707. if (!ConfigureRecording(false))
  708. return false;
  709. if (!obs_output_start(fileOutput)) {
  710. QString error_reason;
  711. const char *error = obs_output_get_last_error(fileOutput);
  712. if (error)
  713. error_reason = QT_UTF8(error);
  714. else
  715. error_reason = QTStr("Output.StartFailedGeneric");
  716. QMessageBox::critical(main, QTStr("Output.StartRecordingFailed"), error_reason);
  717. return false;
  718. }
  719. return true;
  720. }
  721. bool SimpleOutput::StartReplayBuffer()
  722. {
  723. UpdateRecording();
  724. if (!ConfigureRecording(true))
  725. return false;
  726. if (!obs_output_start(replayBuffer)) {
  727. QMessageBox::critical(main, QTStr("Output.StartReplayFailed"), QTStr("Output.StartFailedGeneric"));
  728. return false;
  729. }
  730. return true;
  731. }
  732. void SimpleOutput::StopStreaming(bool force)
  733. {
  734. auto output = StreamingOutput();
  735. if (force && output)
  736. obs_output_force_stop(output);
  737. else if (multitrackVideo && multitrackVideoActive)
  738. multitrackVideo->StopStreaming();
  739. else
  740. obs_output_stop(output);
  741. }
  742. void SimpleOutput::StopRecording(bool force)
  743. {
  744. if (force)
  745. obs_output_force_stop(fileOutput);
  746. else
  747. obs_output_stop(fileOutput);
  748. }
  749. void SimpleOutput::StopReplayBuffer(bool force)
  750. {
  751. if (force)
  752. obs_output_force_stop(replayBuffer);
  753. else
  754. obs_output_stop(replayBuffer);
  755. }
  756. bool SimpleOutput::StreamingActive() const
  757. {
  758. return obs_output_active(StreamingOutput());
  759. }
  760. bool SimpleOutput::RecordingActive() const
  761. {
  762. return obs_output_active(fileOutput);
  763. }
  764. bool SimpleOutput::ReplayBufferActive() const
  765. {
  766. return obs_output_active(replayBuffer);
  767. }