|
17 | 17 | ******************************************************************************/ |
18 | 18 |
|
19 | 19 | #include "nodeobs_autoconfig.h" |
| 20 | +#include <algorithm> |
20 | 21 | #include <array> |
21 | 22 | #include <future> |
22 | 23 | #include <set> |
@@ -558,7 +559,34 @@ void autoConfig::TestBandwidthThreadV2(void) |
558 | 559 | type = "rtmp_output"; |
559 | 560 | std::string outputName = "autoconfig_bw_" + std::to_string(i); |
560 | 561 | targets[i]->CreateOutput(type, outputName); |
561 | | - targets[i]->testBandwidth(gotError); |
| 562 | + |
| 563 | + // Pick a high test bitrate so the measurement reflects the link's |
| 564 | + // real ceiling rather than whatever low value the user has set. |
| 565 | + // - user's current bitrate (might already be high) |
| 566 | + // - platform cap probed via obs_service_apply_encoder_settings |
| 567 | + // (Twitch returns 6000 for non-partners; partners higher) |
| 568 | + // - 6000 fallback when no platform hook exists (custom RTMP, etc.) |
| 569 | + int userBitrate = 0; |
| 570 | + if (targets[i]->videoEncoder) { |
| 571 | + obs_data_t *s = obs_encoder_get_settings(targets[i]->videoEncoder); |
| 572 | + userBitrate = (int)obs_data_get_int(s, "bitrate"); |
| 573 | + obs_data_release(s); |
| 574 | + } |
| 575 | + int platformCap = 0; |
| 576 | + if (targets[i]->service) { |
| 577 | + obs_data_t *probe = obs_data_create(); |
| 578 | + obs_data_set_int(probe, "bitrate", 50000); |
| 579 | + obs_service_apply_encoder_settings(targets[i]->service, probe, nullptr); |
| 580 | + int capped = (int)obs_data_get_int(probe, "bitrate"); |
| 581 | + if (capped > 0 && capped < 50000) |
| 582 | + platformCap = capped; |
| 583 | + obs_data_release(probe); |
| 584 | + } |
| 585 | + int testBitrate = std::max({userBitrate, platformCap, 6000}); |
| 586 | + blog(LOG_INFO, "TestBandwidthV2: target %zu test bitrate %d (user=%d, platformCap=%d)", i, testBitrate, userBitrate, |
| 587 | + platformCap); |
| 588 | + |
| 589 | + targets[i]->testBandwidth(gotError, testBitrate); |
562 | 590 |
|
563 | 591 | if (!gotError && targets[i]->GetOutput()) { |
564 | 592 | testingServices.push_back(targets[i]); |
@@ -647,11 +675,24 @@ void autoConfig::TestBandwidthThreadV2(void) |
647 | 675 | result.ms = elapsedMs; |
648 | 676 | result.targetIndex = testingServiceTargetIdx[si]; |
649 | 677 |
|
650 | | - if (droppedFrames > 0 || (int)bitrate < (runContext.startingBitrate * 75 / 100)) { |
651 | | - result.bitrate = (int)bitrate * 70 / 100; |
652 | | - } else { |
653 | | - result.bitrate = runContext.startingBitrate; |
654 | | - } |
| 678 | + // Use the per-target test bitrate (still active on the encoder |
| 679 | + // until CleanTestMode runs below) as the reference, not the |
| 680 | + // global runContext.startingBitrate which doesn't reflect the |
| 681 | + // per-target ceiling-search override. |
| 682 | + int testBitrateRef = 0; |
| 683 | + if (streaming->videoEncoder) { |
| 684 | + obs_data_t *encSettings = obs_encoder_get_settings(streaming->videoEncoder); |
| 685 | + testBitrateRef = (int)obs_data_get_int(encSettings, "bitrate"); |
| 686 | + obs_data_release(encSettings); |
| 687 | + } |
| 688 | + if (testBitrateRef <= 0) |
| 689 | + testBitrateRef = runContext.startingBitrate; |
| 690 | + |
| 691 | + if (droppedFrames > 0 || (int)bitrate < (testBitrateRef * 75 / 100)) { |
| 692 | + result.bitrate = (int)bitrate * 70 / 100; |
| 693 | + } else { |
| 694 | + result.bitrate = testBitrateRef; |
| 695 | + } |
655 | 696 |
|
656 | 697 | testResults.push_back(result); |
657 | 698 | } |
@@ -1732,8 +1773,8 @@ static void applyResults() |
1732 | 1773 | v.output_width = ((uint32_t)runContext.idealResolutionCX) & 0xFFFFFFFC; |
1733 | 1774 | v.output_height = ((uint32_t)runContext.idealResolutionCY) & 0xFFFFFFFE; |
1734 | 1775 |
|
1735 | | - blog(LOG_INFO, "applyResults: ctx=%p current=%ux%u@%u/%u requested=%ux%u@%u/%u", video, video->output_width, |
1736 | | - video->output_height, video->fps_num, video->fps_den, v.output_width, v.output_height, v.fps_num, v.fps_den); |
| 1776 | + blog(LOG_INFO, "applyResults: ctx=%p current=%ux%u@%u/%u requested=%ux%u@%u/%u", video, video->output_width, video->output_height, |
| 1777 | + video->fps_num, video->fps_den, v.output_width, v.output_height, v.fps_num, v.fps_den); |
1737 | 1778 |
|
1738 | 1779 | // Skip the libobs call when nothing changes — the common case where |
1739 | 1780 | // autoconfig picks the resolution/FPS the canvas is already running at. |
@@ -1775,33 +1816,49 @@ static void applyResults() |
1775 | 1816 | blog(LOG_INFO, "applyResults: target %llu: applied service server '%s'", st.id, targetServer.c_str()); |
1776 | 1817 | } |
1777 | 1818 |
|
1778 | | - // Streaming bitrate — three caps applied in order: |
1779 | | - // a) EstimateUpperBitrate: theoretical max for the chosen res/FPS |
1780 | | - // b) Hardware encoder headroom: 14% multiplier |
1781 | | - // c) Platform cap via obs_service_apply_encoder_settings (per-target) |
| 1819 | + // Streaming bitrate selection (Option 2 — heuristic as floor, not ceiling): |
| 1820 | + // 1. Read user's current bitrate (CleanTestMode restored it before this). |
| 1821 | + // 2. Compute OBS quality heuristic for the chosen res/FPS. |
| 1822 | + // 3. Pick max(user, heuristic) — respect user when above the heuristic; |
| 1823 | + // otherwise use the heuristic as the recommendation. |
| 1824 | + // 4. Cap by what the bandwidth test actually delivered (targetBitrate). |
| 1825 | + // 5. Cap by the per-platform service cap (Twitch etc.). |
1782 | 1826 | if (st.streaming->videoEncoder && targetBitrate > 0) { |
| 1827 | + int userBitrate = 0; |
| 1828 | + { |
| 1829 | + obs_data_t *s = obs_encoder_get_settings(st.streaming->videoEncoder); |
| 1830 | + userBitrate = (int)obs_data_get_int(s, "bitrate"); |
| 1831 | + obs_data_release(s); |
| 1832 | + } |
| 1833 | + |
1783 | 1834 | long double upperBitrate_d = EstimateUpperBitrate((int)runContext.idealResolutionCX, (int)runContext.idealResolutionCY, |
1784 | 1835 | runContext.idealFPSNum, runContext.idealFPSDen ? runContext.idealFPSDen : 1); |
1785 | | - uint64_t upperBitrate = (uint64_t)(std::floor(upperBitrate_d / 50.0L) * 50.0L); |
1786 | | - if (runContext.streamingEncoder != Encoder::x264 && upperBitrate > 0) { |
1787 | | - upperBitrate = upperBitrate * 114ULL / 100ULL; |
| 1836 | + uint64_t heuristic = (uint64_t)(std::floor(upperBitrate_d / 50.0L) * 50.0L); |
| 1837 | + if (runContext.streamingEncoder != Encoder::x264 && heuristic > 0) { |
| 1838 | + heuristic = heuristic * 114ULL / 100ULL; |
1788 | 1839 | } |
1789 | | - uint64_t finalBitrate = targetBitrate; |
1790 | | - if (upperBitrate > 0 && finalBitrate > upperBitrate) |
1791 | | - finalBitrate = upperBitrate; |
1792 | 1840 |
|
| 1841 | + uint64_t finalBitrate = ((uint64_t)userBitrate > heuristic) ? (uint64_t)userBitrate : heuristic; |
| 1842 | + uint64_t preMeasured = finalBitrate; |
| 1843 | + if (finalBitrate > targetBitrate) |
| 1844 | + finalBitrate = targetBitrate; |
| 1845 | + |
| 1846 | + uint64_t platformCapped = 0; |
1793 | 1847 | if (st.streaming->service) { |
1794 | 1848 | obs_data_t *capSettings = obs_data_create(); |
1795 | 1849 | obs_data_set_int(capSettings, "bitrate", (long long)finalBitrate); |
1796 | 1850 | obs_service_apply_encoder_settings(st.streaming->service, capSettings, nullptr); |
1797 | | - uint64_t platformCapped = (uint64_t)obs_data_get_int(capSettings, "bitrate"); |
| 1851 | + platformCapped = (uint64_t)obs_data_get_int(capSettings, "bitrate"); |
1798 | 1852 | if (platformCapped > 0 && platformCapped < finalBitrate) { |
1799 | | - blog(LOG_INFO, "applyResults: platform cap reduced bitrate from %llu to %llu", finalBitrate, platformCapped); |
1800 | 1853 | finalBitrate = platformCapped; |
1801 | 1854 | } |
1802 | 1855 | obs_data_release(capSettings); |
1803 | 1856 | } |
1804 | 1857 |
|
| 1858 | + blog(LOG_INFO, |
| 1859 | + "applyResults: target %llu picked %llu (user=%d heuristic=%llu choseBeforeCaps=%llu measured=%llu platformCap=%llu)", |
| 1860 | + st.id, finalBitrate, userBitrate, heuristic, preMeasured, targetBitrate, platformCapped); |
| 1861 | + |
1805 | 1862 | obs_data_t *encSettings = obs_data_create(); |
1806 | 1863 | obs_data_set_int(encSettings, "bitrate", (long long)finalBitrate); |
1807 | 1864 | obs_encoder_update(st.streaming->videoEncoder, encSettings); |
|
0 commit comments