window-basic-main-outputs.cpp 32 KB

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