window-basic-main-outputs.cpp 28 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033
  1. #include <string>
  2. #include <QMessageBox>
  3. #include "audio-encoders.hpp"
  4. #include "window-basic-main.hpp"
  5. #include "window-basic-main-outputs.hpp"
  6. using namespace std;
  7. static void OBSStreamStarting(void *data, calldata_t *params)
  8. {
  9. BasicOutputHandler *output = static_cast<BasicOutputHandler*>(data);
  10. obs_output_t *obj = (obs_output_t*)calldata_ptr(params, "output");
  11. int sec = (int)obs_output_get_active_delay(obj);
  12. if (sec == 0)
  13. return;
  14. output->delayActive = true;
  15. QMetaObject::invokeMethod(output->main,
  16. "StreamDelayStarting", Q_ARG(int, sec));
  17. }
  18. static void OBSStreamStopping(void *data, calldata_t *params)
  19. {
  20. BasicOutputHandler *output = static_cast<BasicOutputHandler*>(data);
  21. obs_output_t *obj = (obs_output_t*)calldata_ptr(params, "output");
  22. int sec = (int)obs_output_get_active_delay(obj);
  23. if (sec == 0)
  24. return;
  25. QMetaObject::invokeMethod(output->main,
  26. "StreamDelayStopping", Q_ARG(int, sec));
  27. }
  28. static void OBSStartStreaming(void *data, calldata_t *params)
  29. {
  30. BasicOutputHandler *output = static_cast<BasicOutputHandler*>(data);
  31. output->streamingActive = true;
  32. QMetaObject::invokeMethod(output->main, "StreamingStart");
  33. UNUSED_PARAMETER(params);
  34. }
  35. static void OBSStopStreaming(void *data, calldata_t *params)
  36. {
  37. BasicOutputHandler *output = static_cast<BasicOutputHandler*>(data);
  38. int code = (int)calldata_int(params, "code");
  39. output->streamingActive = false;
  40. output->delayActive = false;
  41. QMetaObject::invokeMethod(output->main,
  42. "StreamingStop", Q_ARG(int, code));
  43. }
  44. static void OBSStartRecording(void *data, calldata_t *params)
  45. {
  46. BasicOutputHandler *output = static_cast<BasicOutputHandler*>(data);
  47. output->recordingActive = true;
  48. QMetaObject::invokeMethod(output->main, "RecordingStart");
  49. UNUSED_PARAMETER(params);
  50. }
  51. static void OBSStopRecording(void *data, calldata_t *params)
  52. {
  53. BasicOutputHandler *output = static_cast<BasicOutputHandler*>(data);
  54. int code = (int)calldata_int(params, "code");
  55. output->recordingActive = false;
  56. QMetaObject::invokeMethod(output->main,
  57. "RecordingStop", Q_ARG(int, code));
  58. UNUSED_PARAMETER(params);
  59. }
  60. /* ------------------------------------------------------------------------ */
  61. static bool CreateAACEncoder(OBSEncoder &res, string &id, int bitrate,
  62. const char *name, size_t idx)
  63. {
  64. const char *id_ = GetAACEncoderForBitrate(bitrate);
  65. if (!id_) {
  66. id.clear();
  67. res = nullptr;
  68. return false;
  69. }
  70. if (id == id_)
  71. return true;
  72. id = id_;
  73. res = obs_audio_encoder_create(id_, name, nullptr, idx, nullptr);
  74. if (res) {
  75. obs_encoder_release(res);
  76. return true;
  77. }
  78. return false;
  79. }
  80. /* ------------------------------------------------------------------------ */
  81. struct SimpleOutput : BasicOutputHandler {
  82. OBSEncoder aacStreaming;
  83. OBSEncoder h264Streaming;
  84. OBSEncoder aacRecording;
  85. OBSEncoder h264Recording;
  86. string aacRecEncID;
  87. string aacStreamEncID;
  88. string videoEncoder;
  89. string videoQuality;
  90. bool usingRecordingPreset = false;
  91. bool ffmpegOutput = false;
  92. bool lowCPUx264 = false;
  93. SimpleOutput(OBSBasic *main_);
  94. int CalcCRF(int crf);
  95. void UpdateRecordingSettings_x264_crf(int crf);
  96. void UpdateRecordingSettings();
  97. void UpdateRecordingAudioSettings();
  98. virtual void Update() override;
  99. void SetupOutputs();
  100. int GetAudioBitrate() const;
  101. void LoadRecordingPreset_x264();
  102. void LoadRecordingPreset_Lossless();
  103. void LoadRecordingPreset();
  104. virtual bool StartStreaming(obs_service_t *service) override;
  105. virtual bool StartRecording() override;
  106. virtual void StopStreaming() override;
  107. virtual void ForceStopStreaming() override;
  108. virtual void StopRecording() override;
  109. virtual bool StreamingActive() const override;
  110. virtual bool RecordingActive() const override;
  111. };
  112. void SimpleOutput::LoadRecordingPreset_Lossless()
  113. {
  114. fileOutput = obs_output_create("ffmpeg_output",
  115. "simple_ffmpeg_output", nullptr, nullptr);
  116. if (!fileOutput)
  117. throw "Failed to create recording FFmpeg output "
  118. "(simple output)";
  119. obs_output_release(fileOutput);
  120. obs_data_t *settings = obs_data_create();
  121. obs_data_set_string(settings, "format_name", "mkv");
  122. obs_data_set_string(settings, "video_encoder", "utvideo");
  123. obs_data_set_string(settings, "audio_encoder", "flac");
  124. obs_output_update(fileOutput, settings);
  125. obs_data_release(settings);
  126. }
  127. void SimpleOutput::LoadRecordingPreset_x264()
  128. {
  129. h264Recording = obs_video_encoder_create("obs_x264",
  130. "simple_h264_recording", nullptr, nullptr);
  131. if (!h264Recording)
  132. throw "Failed to create h264 recording encoder (simple output)";
  133. obs_encoder_release(h264Recording);
  134. if (!CreateAACEncoder(aacRecording, aacRecEncID, 192,
  135. "simple_aac_recording", 0))
  136. throw "Failed to create aac recording encoder (simple output)";
  137. }
  138. void SimpleOutput::LoadRecordingPreset()
  139. {
  140. const char *quality = config_get_string(main->Config(), "SimpleOutput",
  141. "RecQuality");
  142. const char *encoder = config_get_string(main->Config(), "SimpleOutput",
  143. "RecEncoder");
  144. videoEncoder = encoder;
  145. videoQuality = quality;
  146. ffmpegOutput = false;
  147. if (strcmp(quality, "Stream") == 0) {
  148. h264Recording = h264Streaming;
  149. aacRecording = aacStreaming;
  150. usingRecordingPreset = false;
  151. return;
  152. } else if (strcmp(quality, "Lossless") == 0) {
  153. LoadRecordingPreset_Lossless();
  154. usingRecordingPreset = true;
  155. ffmpegOutput = true;
  156. return;
  157. } else {
  158. lowCPUx264 = strcmp(encoder, SIMPLE_ENCODER_X264_LOWCPU) == 0;
  159. LoadRecordingPreset_x264();
  160. usingRecordingPreset = true;
  161. }
  162. }
  163. SimpleOutput::SimpleOutput(OBSBasic *main_) : BasicOutputHandler(main_)
  164. {
  165. streamOutput = obs_output_create("rtmp_output", "simple_stream",
  166. nullptr, nullptr);
  167. if (!streamOutput)
  168. throw "Failed to create stream output (simple output)";
  169. obs_output_release(streamOutput);
  170. h264Streaming = obs_video_encoder_create("obs_x264",
  171. "simple_h264_stream", nullptr, nullptr);
  172. if (!h264Streaming)
  173. throw "Failed to create h264 streaming encoder (simple output)";
  174. obs_encoder_release(h264Streaming);
  175. if (!CreateAACEncoder(aacStreaming, aacStreamEncID, GetAudioBitrate(),
  176. "simple_aac", 0))
  177. throw "Failed to create aac streaming encoder (simple output)";
  178. streamDelayStarting.Connect(obs_output_get_signal_handler(streamOutput),
  179. "starting", OBSStreamStarting, this);
  180. streamDelayStopping.Connect(obs_output_get_signal_handler(streamOutput),
  181. "stopping", OBSStreamStopping, this);
  182. startStreaming.Connect(obs_output_get_signal_handler(streamOutput),
  183. "start", OBSStartStreaming, this);
  184. stopStreaming.Connect(obs_output_get_signal_handler(streamOutput),
  185. "stop", OBSStopStreaming, this);
  186. LoadRecordingPreset();
  187. if (!ffmpegOutput) {
  188. fileOutput = obs_output_create("ffmpeg_muxer",
  189. "simple_file_output", nullptr, nullptr);
  190. if (!fileOutput)
  191. throw "Failed to create recording output "
  192. "(simple output)";
  193. obs_output_release(fileOutput);
  194. }
  195. startRecording.Connect(obs_output_get_signal_handler(fileOutput),
  196. "start", OBSStartRecording, this);
  197. stopRecording.Connect(obs_output_get_signal_handler(fileOutput),
  198. "stop", OBSStopRecording, this);
  199. }
  200. int SimpleOutput::GetAudioBitrate() const
  201. {
  202. int bitrate = (int)config_get_uint(main->Config(), "SimpleOutput",
  203. "ABitrate");
  204. return FindClosestAvailableAACBitrate(bitrate);
  205. }
  206. void SimpleOutput::Update()
  207. {
  208. obs_data_t *h264Settings = obs_data_create();
  209. obs_data_t *aacSettings = obs_data_create();
  210. int videoBitrate = config_get_uint(main->Config(), "SimpleOutput",
  211. "VBitrate");
  212. int audioBitrate = GetAudioBitrate();
  213. bool advanced = config_get_bool(main->Config(), "SimpleOutput",
  214. "UseAdvanced");
  215. const char *preset = config_get_string(main->Config(),
  216. "SimpleOutput", "Preset");
  217. const char *custom = config_get_string(main->Config(),
  218. "SimpleOutput", "x264Settings");
  219. obs_data_set_int(h264Settings, "bitrate", videoBitrate);
  220. if (advanced) {
  221. obs_data_set_string(h264Settings, "preset", preset);
  222. obs_data_set_string(h264Settings, "x264opts", custom);
  223. }
  224. obs_data_set_bool(aacSettings, "cbr", true);
  225. obs_data_set_int(aacSettings, "bitrate", audioBitrate);
  226. obs_service_apply_encoder_settings(main->GetService(),
  227. h264Settings, aacSettings);
  228. video_t *video = obs_get_video();
  229. enum video_format format = video_output_get_format(video);
  230. if (format != VIDEO_FORMAT_NV12 && format != VIDEO_FORMAT_I420)
  231. obs_encoder_set_preferred_video_format(h264Streaming,
  232. VIDEO_FORMAT_NV12);
  233. obs_encoder_update(h264Streaming, h264Settings);
  234. obs_encoder_update(aacStreaming, aacSettings);
  235. obs_data_release(h264Settings);
  236. obs_data_release(aacSettings);
  237. }
  238. void SimpleOutput::UpdateRecordingAudioSettings()
  239. {
  240. obs_data_t *settings = obs_data_create();
  241. obs_data_set_int(settings, "bitrate", 192);
  242. obs_data_set_bool(settings, "cbr", true);
  243. obs_encoder_update(aacRecording, settings);
  244. obs_data_release(settings);
  245. }
  246. #define CROSS_DIST_CUTOFF 2000.0
  247. int SimpleOutput::CalcCRF(int crf)
  248. {
  249. int cx = config_get_uint(main->Config(), "Video", "OutputCX");
  250. int cy = config_get_uint(main->Config(), "Video", "OutputCY");
  251. double fCX = double(cx);
  252. double fCY = double(cy);
  253. if (lowCPUx264)
  254. crf -= 2;
  255. double crossDist = sqrt(fCX * fCX + fCY * fCY);
  256. double crfResReduction =
  257. fmin(CROSS_DIST_CUTOFF, crossDist) / CROSS_DIST_CUTOFF;
  258. crfResReduction = (1.0 - crfResReduction) * 10.0;
  259. return crf - int(crfResReduction);
  260. }
  261. void SimpleOutput::UpdateRecordingSettings_x264_crf(int crf)
  262. {
  263. obs_data_t *settings = obs_data_create();
  264. obs_data_set_int(settings, "bitrate", 1000);
  265. obs_data_set_int(settings, "buffer_size", 0);
  266. obs_data_set_int(settings, "crf", crf);
  267. obs_data_set_bool(settings, "use_bufsize", true);
  268. obs_data_set_bool(settings, "cbr", false);
  269. obs_data_set_string(settings, "profile", "high");
  270. obs_data_set_string(settings, "preset",
  271. lowCPUx264 ? "ultrafast" : "veryfast");
  272. obs_encoder_update(h264Recording, settings);
  273. obs_data_release(settings);
  274. }
  275. void SimpleOutput::UpdateRecordingSettings()
  276. {
  277. if (astrcmp_n(videoEncoder.c_str(), "x264", 4) == 0) {
  278. if (videoQuality == "Small")
  279. UpdateRecordingSettings_x264_crf(CalcCRF(23));
  280. else if (videoQuality == "HQ")
  281. UpdateRecordingSettings_x264_crf(CalcCRF(16));
  282. }
  283. }
  284. inline void SimpleOutput::SetupOutputs()
  285. {
  286. SimpleOutput::Update();
  287. obs_encoder_set_video(h264Streaming, obs_get_video());
  288. obs_encoder_set_audio(aacStreaming, obs_get_audio());
  289. if (usingRecordingPreset) {
  290. if (ffmpegOutput) {
  291. obs_output_set_media(fileOutput, obs_get_video(),
  292. obs_get_audio());
  293. } else {
  294. obs_encoder_set_video(h264Recording, obs_get_video());
  295. obs_encoder_set_audio(aacRecording, obs_get_audio());
  296. }
  297. }
  298. }
  299. bool SimpleOutput::StartStreaming(obs_service_t *service)
  300. {
  301. if (!Active())
  302. SetupOutputs();
  303. obs_output_set_video_encoder(streamOutput, h264Streaming);
  304. obs_output_set_audio_encoder(streamOutput, aacStreaming, 0);
  305. obs_output_set_service(streamOutput, service);
  306. bool reconnect = config_get_bool(main->Config(), "Output",
  307. "Reconnect");
  308. int retryDelay = config_get_uint(main->Config(), "Output",
  309. "RetryDelay");
  310. int maxRetries = config_get_uint(main->Config(), "Output",
  311. "MaxRetries");
  312. bool useDelay = config_get_bool(main->Config(), "Output",
  313. "DelayEnable");
  314. int delaySec = config_get_int(main->Config(), "Output",
  315. "DelaySec");
  316. bool preserveDelay = config_get_bool(main->Config(), "Output",
  317. "DelayPreserve");
  318. if (!reconnect)
  319. maxRetries = 0;
  320. obs_output_set_delay(streamOutput, useDelay ? delaySec : 0,
  321. preserveDelay ? OBS_OUTPUT_DELAY_PRESERVE : 0);
  322. obs_output_set_reconnect_settings(streamOutput, maxRetries,
  323. retryDelay);
  324. if (obs_output_start(streamOutput)) {
  325. return true;
  326. }
  327. return false;
  328. }
  329. bool SimpleOutput::StartRecording()
  330. {
  331. if (usingRecordingPreset) {
  332. if (!ffmpegOutput)
  333. UpdateRecordingSettings();
  334. } else if (!obs_output_active(streamOutput)) {
  335. Update();
  336. }
  337. if (!Active())
  338. SetupOutputs();
  339. const char *path = config_get_string(main->Config(),
  340. "SimpleOutput", "FilePath");
  341. const char *format = config_get_string(main->Config(),
  342. "SimpleOutput", "RecFormat");
  343. const char *mux = config_get_string(main->Config(), "SimpleOutput",
  344. "MuxerCustom");
  345. bool noSpace = config_get_bool(main->Config(), "SimpleOutput",
  346. "FileNameWithoutSpace");
  347. os_dir_t *dir = path ? os_opendir(path) : nullptr;
  348. if (!dir) {
  349. QMessageBox::information(main,
  350. QTStr("Output.BadPath.Title"),
  351. QTStr("Output.BadPath.Text"));
  352. return false;
  353. }
  354. os_closedir(dir);
  355. string strPath;
  356. strPath += path;
  357. char lastChar = strPath.back();
  358. if (lastChar != '/' && lastChar != '\\')
  359. strPath += "/";
  360. strPath += GenerateTimeDateFilename(ffmpegOutput ? "mkv" : format,
  361. noSpace);
  362. SetupOutputs();
  363. if (!ffmpegOutput) {
  364. obs_output_set_video_encoder(fileOutput, h264Recording);
  365. obs_output_set_audio_encoder(fileOutput, aacRecording, 0);
  366. }
  367. obs_data_t *settings = obs_data_create();
  368. obs_data_set_string(settings, ffmpegOutput ? "url" : "path",
  369. strPath.c_str());
  370. obs_data_set_string(settings, "muxer_settings", mux);
  371. obs_output_update(fileOutput, settings);
  372. obs_data_release(settings);
  373. if (obs_output_start(fileOutput)) {
  374. return true;
  375. }
  376. return false;
  377. }
  378. void SimpleOutput::StopStreaming()
  379. {
  380. obs_output_stop(streamOutput);
  381. }
  382. void SimpleOutput::ForceStopStreaming()
  383. {
  384. obs_output_force_stop(streamOutput);
  385. }
  386. void SimpleOutput::StopRecording()
  387. {
  388. obs_output_stop(fileOutput);
  389. }
  390. bool SimpleOutput::StreamingActive() const
  391. {
  392. return obs_output_active(streamOutput);
  393. }
  394. bool SimpleOutput::RecordingActive() const
  395. {
  396. return obs_output_active(fileOutput);
  397. }
  398. /* ------------------------------------------------------------------------ */
  399. struct AdvancedOutput : BasicOutputHandler {
  400. OBSEncoder aacTrack[4];
  401. OBSEncoder h264Streaming;
  402. OBSEncoder h264Recording;
  403. bool ffmpegOutput;
  404. bool ffmpegRecording;
  405. bool useStreamEncoder;
  406. string aacEncoderID[4];
  407. AdvancedOutput(OBSBasic *main_);
  408. inline void UpdateStreamSettings();
  409. inline void UpdateRecordingSettings();
  410. inline void UpdateAudioSettings();
  411. virtual void Update() override;
  412. inline void SetupStreaming();
  413. inline void SetupRecording();
  414. inline void SetupFFmpeg();
  415. void SetupOutputs();
  416. int GetAudioBitrate(size_t i) const;
  417. virtual bool StartStreaming(obs_service_t *service) override;
  418. virtual bool StartRecording() override;
  419. virtual void StopStreaming() override;
  420. virtual void ForceStopStreaming() override;
  421. virtual void StopRecording() override;
  422. virtual bool StreamingActive() const override;
  423. virtual bool RecordingActive() const override;
  424. };
  425. static OBSData GetDataFromJsonFile(const char *jsonFile)
  426. {
  427. char fullPath[512];
  428. int ret = GetProfilePath(fullPath, sizeof(fullPath), jsonFile);
  429. if (ret > 0) {
  430. BPtr<char> jsonData = os_quick_read_utf8_file(fullPath);
  431. if (!!jsonData) {
  432. obs_data_t *data = obs_data_create_from_json(jsonData);
  433. OBSData dataRet(data);
  434. obs_data_release(data);
  435. return dataRet;
  436. }
  437. }
  438. return nullptr;
  439. }
  440. AdvancedOutput::AdvancedOutput(OBSBasic *main_) : BasicOutputHandler(main_)
  441. {
  442. const char *recType = config_get_string(main->Config(), "AdvOut",
  443. "RecType");
  444. const char *streamEncoder = config_get_string(main->Config(), "AdvOut",
  445. "Encoder");
  446. const char *recordEncoder = config_get_string(main->Config(), "AdvOut",
  447. "RecEncoder");
  448. ffmpegOutput = astrcmpi(recType, "FFmpeg") == 0;
  449. ffmpegRecording = ffmpegOutput &&
  450. config_get_bool(main->Config(), "AdvOut", "FFOutputToFile");
  451. useStreamEncoder = astrcmpi(recordEncoder, "none") == 0;
  452. OBSData streamEncSettings = GetDataFromJsonFile("streamEncoder.json");
  453. OBSData recordEncSettings = GetDataFromJsonFile("recordEncoder.json");
  454. streamOutput = obs_output_create("rtmp_output", "adv_stream",
  455. nullptr, nullptr);
  456. if (!streamOutput)
  457. throw "Failed to create stream output (advanced output)";
  458. obs_output_release(streamOutput);
  459. if (ffmpegOutput) {
  460. fileOutput = obs_output_create("ffmpeg_output",
  461. "adv_ffmpeg_output", nullptr, nullptr);
  462. if (!fileOutput)
  463. throw "Failed to create recording FFmpeg output "
  464. "(advanced output)";
  465. obs_output_release(fileOutput);
  466. } else {
  467. fileOutput = obs_output_create("ffmpeg_muxer",
  468. "adv_file_output", nullptr, nullptr);
  469. if (!fileOutput)
  470. throw "Failed to create recording output "
  471. "(advanced output)";
  472. obs_output_release(fileOutput);
  473. if (!useStreamEncoder) {
  474. h264Recording = obs_video_encoder_create(recordEncoder,
  475. "recording_h264", recordEncSettings,
  476. nullptr);
  477. if (!h264Recording)
  478. throw "Failed to create recording h264 "
  479. "encoder (advanced output)";
  480. obs_encoder_release(h264Recording);
  481. }
  482. }
  483. h264Streaming = obs_video_encoder_create(streamEncoder,
  484. "streaming_h264", streamEncSettings, nullptr);
  485. if (!h264Streaming)
  486. throw "Failed to create streaming h264 encoder "
  487. "(advanced output)";
  488. obs_encoder_release(h264Streaming);
  489. for (int i = 0; i < 4; i++) {
  490. char name[9];
  491. sprintf(name, "adv_aac%d", i);
  492. if (!CreateAACEncoder(aacTrack[i], aacEncoderID[i],
  493. GetAudioBitrate(i), name, i))
  494. throw "Failed to create audio encoder "
  495. "(advanced output)";
  496. }
  497. streamDelayStarting.Connect(obs_output_get_signal_handler(streamOutput),
  498. "starting", OBSStreamStarting, this);
  499. streamDelayStopping.Connect(obs_output_get_signal_handler(streamOutput),
  500. "stopping", OBSStreamStopping, this);
  501. startStreaming.Connect(obs_output_get_signal_handler(streamOutput),
  502. "start", OBSStartStreaming, this);
  503. stopStreaming.Connect(obs_output_get_signal_handler(streamOutput),
  504. "stop", OBSStopStreaming, this);
  505. startRecording.Connect(obs_output_get_signal_handler(fileOutput),
  506. "start", OBSStartRecording, this);
  507. stopRecording.Connect(obs_output_get_signal_handler(fileOutput),
  508. "stop", OBSStopRecording, this);
  509. }
  510. void AdvancedOutput::UpdateStreamSettings()
  511. {
  512. bool applyServiceSettings = config_get_bool(main->Config(), "AdvOut",
  513. "ApplyServiceSettings");
  514. OBSData settings = GetDataFromJsonFile("streamEncoder.json");
  515. if (applyServiceSettings)
  516. obs_service_apply_encoder_settings(main->GetService(),
  517. settings, nullptr);
  518. video_t *video = obs_get_video();
  519. enum video_format format = video_output_get_format(video);
  520. if (format != VIDEO_FORMAT_NV12 && format != VIDEO_FORMAT_I420)
  521. obs_encoder_set_preferred_video_format(h264Streaming,
  522. VIDEO_FORMAT_NV12);
  523. obs_encoder_update(h264Streaming, settings);
  524. }
  525. inline void AdvancedOutput::UpdateRecordingSettings()
  526. {
  527. OBSData settings = GetDataFromJsonFile("recordEncoder.json");
  528. obs_encoder_update(h264Recording, settings);
  529. }
  530. void AdvancedOutput::Update()
  531. {
  532. UpdateStreamSettings();
  533. if (!useStreamEncoder && !ffmpegOutput)
  534. UpdateRecordingSettings();
  535. UpdateAudioSettings();
  536. }
  537. inline void AdvancedOutput::SetupStreaming()
  538. {
  539. bool rescale = config_get_bool(main->Config(), "AdvOut",
  540. "Rescale");
  541. const char *rescaleRes = config_get_string(main->Config(), "AdvOut",
  542. "RescaleRes");
  543. bool multitrack = config_get_bool(main->Config(), "AdvOut",
  544. "Multitrack");
  545. int trackIndex = config_get_int(main->Config(), "AdvOut",
  546. "TrackIndex");
  547. int trackCount = config_get_int(main->Config(), "AdvOut",
  548. "TrackCount");
  549. unsigned int cx = 0;
  550. unsigned int cy = 0;
  551. if (rescale && rescaleRes && *rescaleRes) {
  552. if (sscanf(rescaleRes, "%ux%u", &cx, &cy) != 2) {
  553. cx = 0;
  554. cy = 0;
  555. }
  556. }
  557. obs_encoder_set_scaled_size(h264Streaming, cx, cy);
  558. obs_encoder_set_video(h264Streaming, obs_get_video());
  559. obs_output_set_video_encoder(streamOutput, h264Streaming);
  560. if (multitrack) {
  561. int i = 0;
  562. for (; i < trackCount; i++)
  563. obs_output_set_audio_encoder(streamOutput, aacTrack[i],
  564. i);
  565. for (; i < 4; i++)
  566. obs_output_set_audio_encoder(streamOutput, nullptr, i);
  567. } else {
  568. obs_output_set_audio_encoder(streamOutput,
  569. aacTrack[trackIndex - 1], 0);
  570. }
  571. }
  572. inline void AdvancedOutput::SetupRecording()
  573. {
  574. const char *path = config_get_string(main->Config(), "AdvOut",
  575. "RecFilePath");
  576. const char *mux = config_get_string(main->Config(), "AdvOut",
  577. "RecMuxerCustom");
  578. bool rescale = config_get_bool(main->Config(), "AdvOut",
  579. "RecRescale");
  580. const char *rescaleRes = config_get_string(main->Config(), "AdvOut",
  581. "RecRescaleRes");
  582. int tracks = config_get_int(main->Config(), "AdvOut", "RecTracks");
  583. obs_data_t *settings = obs_data_create();
  584. unsigned int cx = 0;
  585. unsigned int cy = 0;
  586. int idx = 0;
  587. if (useStreamEncoder) {
  588. obs_output_set_video_encoder(fileOutput, h264Streaming);
  589. } else {
  590. if (rescale && rescaleRes && *rescaleRes) {
  591. if (sscanf(rescaleRes, "%ux%u", &cx, &cy) != 2) {
  592. cx = 0;
  593. cy = 0;
  594. }
  595. }
  596. obs_encoder_set_scaled_size(h264Recording, cx, cy);
  597. obs_encoder_set_video(h264Recording, obs_get_video());
  598. obs_output_set_video_encoder(fileOutput, h264Recording);
  599. }
  600. for (int i = 0; i < MAX_AUDIO_MIXES; i++) {
  601. if ((tracks & (1<<i)) != 0) {
  602. obs_output_set_audio_encoder(fileOutput, aacTrack[i],
  603. idx++);
  604. }
  605. }
  606. obs_data_set_string(settings, "path", path);
  607. obs_data_set_string(settings, "muxer_settings", mux);
  608. obs_output_update(fileOutput, settings);
  609. obs_data_release(settings);
  610. }
  611. inline void AdvancedOutput::SetupFFmpeg()
  612. {
  613. const char *url = config_get_string(main->Config(), "AdvOut", "FFURL");
  614. int vBitrate = config_get_int(main->Config(), "AdvOut",
  615. "FFVBitrate");
  616. bool rescale = config_get_bool(main->Config(), "AdvOut",
  617. "FFRescale");
  618. const char *rescaleRes = config_get_string(main->Config(), "AdvOut",
  619. "FFRescaleRes");
  620. const char *formatName = config_get_string(main->Config(), "AdvOut",
  621. "FFFormat");
  622. const char *mimeType = config_get_string(main->Config(), "AdvOut",
  623. "FFFormatMimeType");
  624. const char *muxCustom = config_get_string(main->Config(), "AdvOut",
  625. "FFMCustom");
  626. const char *vEncoder = config_get_string(main->Config(), "AdvOut",
  627. "FFVEncoder");
  628. int vEncoderId = config_get_int(main->Config(), "AdvOut",
  629. "FFVEncoderId");
  630. const char *vEncCustom = config_get_string(main->Config(), "AdvOut",
  631. "FFVCustom");
  632. int aBitrate = config_get_int(main->Config(), "AdvOut",
  633. "FFABitrate");
  634. int aTrack = config_get_int(main->Config(), "AdvOut",
  635. "FFAudioTrack");
  636. const char *aEncoder = config_get_string(main->Config(), "AdvOut",
  637. "FFAEncoder");
  638. int aEncoderId = config_get_int(main->Config(), "AdvOut",
  639. "FFAEncoderId");
  640. const char *aEncCustom = config_get_string(main->Config(), "AdvOut",
  641. "FFACustom");
  642. obs_data_t *settings = obs_data_create();
  643. obs_data_set_string(settings, "url", url);
  644. obs_data_set_string(settings, "format_name", formatName);
  645. obs_data_set_string(settings, "format_mime_type", mimeType);
  646. obs_data_set_string(settings, "muxer_settings", muxCustom);
  647. obs_data_set_int(settings, "video_bitrate", vBitrate);
  648. obs_data_set_string(settings, "video_encoder", vEncoder);
  649. obs_data_set_int(settings, "video_encoder_id", vEncoderId);
  650. obs_data_set_string(settings, "video_settings", vEncCustom);
  651. obs_data_set_int(settings, "audio_bitrate", aBitrate);
  652. obs_data_set_string(settings, "audio_encoder", aEncoder);
  653. obs_data_set_int(settings, "audio_encoder_id", aEncoderId);
  654. obs_data_set_string(settings, "audio_settings", aEncCustom);
  655. if (rescale && rescaleRes && *rescaleRes) {
  656. int width;
  657. int height;
  658. int val = sscanf(rescaleRes, "%dx%d", &width, &height);
  659. if (val == 2 && width && height) {
  660. obs_data_set_int(settings, "scale_width", width);
  661. obs_data_set_int(settings, "scale_height", height);
  662. }
  663. }
  664. obs_output_set_mixer(fileOutput, aTrack - 1);
  665. obs_output_set_media(fileOutput, obs_get_video(), obs_get_audio());
  666. obs_output_update(fileOutput, settings);
  667. obs_data_release(settings);
  668. }
  669. static inline void SetEncoderName(obs_encoder_t *encoder, const char *name,
  670. const char *defaultName)
  671. {
  672. obs_encoder_set_name(encoder, (name && *name) ? name : defaultName);
  673. }
  674. inline void AdvancedOutput::UpdateAudioSettings()
  675. {
  676. const char *name1 = config_get_string(main->Config(), "AdvOut",
  677. "Track1Name");
  678. const char *name2 = config_get_string(main->Config(), "AdvOut",
  679. "Track2Name");
  680. const char *name3 = config_get_string(main->Config(), "AdvOut",
  681. "Track3Name");
  682. const char *name4 = config_get_string(main->Config(), "AdvOut",
  683. "Track4Name");
  684. bool applyServiceSettings = config_get_bool(main->Config(), "AdvOut",
  685. "ApplyServiceSettings");
  686. int streamTrackIndex = config_get_int(main->Config(), "AdvOut",
  687. "TrackIndex");
  688. obs_data_t *settings[4];
  689. for (size_t i = 0; i < 4; i++) {
  690. settings[i] = obs_data_create();
  691. obs_data_set_int(settings[i], "bitrate", GetAudioBitrate(i));
  692. }
  693. SetEncoderName(aacTrack[0], name1, "Track1");
  694. SetEncoderName(aacTrack[1], name2, "Track2");
  695. SetEncoderName(aacTrack[2], name3, "Track3");
  696. SetEncoderName(aacTrack[3], name4, "Track4");
  697. for (size_t i = 0; i < 4; i++) {
  698. if (applyServiceSettings && (int)(i + 1) == streamTrackIndex)
  699. obs_service_apply_encoder_settings(main->GetService(),
  700. nullptr, settings[i]);
  701. obs_encoder_update(aacTrack[i], settings[i]);
  702. obs_data_release(settings[i]);
  703. }
  704. }
  705. void AdvancedOutput::SetupOutputs()
  706. {
  707. obs_encoder_set_video(h264Streaming, obs_get_video());
  708. if (h264Recording)
  709. obs_encoder_set_video(h264Recording, obs_get_video());
  710. obs_encoder_set_audio(aacTrack[0], obs_get_audio());
  711. obs_encoder_set_audio(aacTrack[1], obs_get_audio());
  712. obs_encoder_set_audio(aacTrack[2], obs_get_audio());
  713. obs_encoder_set_audio(aacTrack[3], obs_get_audio());
  714. SetupStreaming();
  715. if (ffmpegOutput)
  716. SetupFFmpeg();
  717. else
  718. SetupRecording();
  719. }
  720. int AdvancedOutput::GetAudioBitrate(size_t i) const
  721. {
  722. const char *names[] = {
  723. "Track1Bitrate", "Track2Bitrate",
  724. "Track3Bitrate", "Track4Bitrate",
  725. };
  726. int bitrate = (int)config_get_uint(main->Config(), "AdvOut", names[i]);
  727. return FindClosestAvailableAACBitrate(bitrate);
  728. }
  729. bool AdvancedOutput::StartStreaming(obs_service_t *service)
  730. {
  731. if (!useStreamEncoder ||
  732. (!ffmpegOutput && !obs_output_active(fileOutput))) {
  733. UpdateStreamSettings();
  734. }
  735. UpdateAudioSettings();
  736. if (!Active())
  737. SetupOutputs();
  738. obs_output_set_service(streamOutput, service);
  739. bool reconnect = config_get_bool(main->Config(), "Output", "Reconnect");
  740. int retryDelay = config_get_int(main->Config(), "Output", "RetryDelay");
  741. int maxRetries = config_get_int(main->Config(), "Output", "MaxRetries");
  742. bool useDelay = config_get_bool(main->Config(), "Output",
  743. "DelayEnable");
  744. int delaySec = config_get_int(main->Config(), "Output",
  745. "DelaySec");
  746. bool preserveDelay = config_get_bool(main->Config(), "Output",
  747. "DelayPreserve");
  748. if (!reconnect)
  749. maxRetries = 0;
  750. obs_output_set_delay(streamOutput, useDelay ? delaySec : 0,
  751. preserveDelay ? OBS_OUTPUT_DELAY_PRESERVE : 0);
  752. obs_output_set_reconnect_settings(streamOutput, maxRetries,
  753. retryDelay);
  754. if (obs_output_start(streamOutput)) {
  755. return true;
  756. }
  757. return false;
  758. }
  759. bool AdvancedOutput::StartRecording()
  760. {
  761. const char *path;
  762. const char *format;
  763. bool noSpace = false;
  764. if (!useStreamEncoder) {
  765. if (!ffmpegOutput) {
  766. UpdateRecordingSettings();
  767. }
  768. } else if (!obs_output_active(streamOutput)) {
  769. UpdateStreamSettings();
  770. }
  771. UpdateAudioSettings();
  772. if (!Active())
  773. SetupOutputs();
  774. if (!ffmpegOutput || ffmpegRecording) {
  775. path = config_get_string(main->Config(), "AdvOut",
  776. ffmpegRecording ? "FFFilePath" : "RecFilePath");
  777. format = config_get_string(main->Config(), "AdvOut",
  778. ffmpegRecording ? "FFExtension" : "RecFormat");
  779. noSpace = config_get_bool(main->Config(), "AdvOut",
  780. ffmpegRecording ?
  781. "FFFileNameWithoutSpace" :
  782. "RecFileNameWithoutSpace");
  783. os_dir_t *dir = path ? os_opendir(path) : nullptr;
  784. if (!dir) {
  785. QMessageBox::information(main,
  786. QTStr("Output.BadPath.Title"),
  787. QTStr("Output.BadPath.Text"));
  788. return false;
  789. }
  790. os_closedir(dir);
  791. string strPath;
  792. strPath += path;
  793. char lastChar = strPath.back();
  794. if (lastChar != '/' && lastChar != '\\')
  795. strPath += "/";
  796. strPath += GenerateTimeDateFilename(format, noSpace);
  797. obs_data_t *settings = obs_data_create();
  798. obs_data_set_string(settings,
  799. ffmpegRecording ? "url" : "path",
  800. strPath.c_str());
  801. obs_output_update(fileOutput, settings);
  802. obs_data_release(settings);
  803. }
  804. if (obs_output_start(fileOutput)) {
  805. return true;
  806. }
  807. return false;
  808. }
  809. void AdvancedOutput::StopStreaming()
  810. {
  811. obs_output_stop(streamOutput);
  812. }
  813. void AdvancedOutput::ForceStopStreaming()
  814. {
  815. obs_output_force_stop(streamOutput);
  816. }
  817. void AdvancedOutput::StopRecording()
  818. {
  819. obs_output_stop(fileOutput);
  820. }
  821. bool AdvancedOutput::StreamingActive() const
  822. {
  823. return obs_output_active(streamOutput);
  824. }
  825. bool AdvancedOutput::RecordingActive() const
  826. {
  827. return obs_output_active(fileOutput);
  828. }
  829. /* ------------------------------------------------------------------------ */
  830. BasicOutputHandler *CreateSimpleOutputHandler(OBSBasic *main)
  831. {
  832. return new SimpleOutput(main);
  833. }
  834. BasicOutputHandler *CreateAdvancedOutputHandler(OBSBasic *main)
  835. {
  836. return new AdvancedOutput(main);
  837. }