window-basic-main-outputs.cpp 32 KB

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