diff --git a/tools/chatgui/ffmpeg/build_ffmpeg.sh b/tools/chatgui/ffmpeg/build_ffmpeg.sh index 78206df2..8448a670 100755 --- a/tools/chatgui/ffmpeg/build_ffmpeg.sh +++ b/tools/chatgui/ffmpeg/build_ffmpeg.sh @@ -7,7 +7,7 @@ SRC_DIR="$BUILD_DIR/src" INSTALL_DIR="$BUILD_DIR/install" JOBS=$(nproc 2>/dev/null || echo 4) AMF_HEADERS_VERSION="${AMF_HEADERS_VERSION:-1.5.3}" -FFMPEG_PROFILE_MARKER="$INSTALL_DIR/share/utun-chatgui-ffmpeg-minimal-v3" +FFMPEG_PROFILE_MARKER="$INSTALL_DIR/share/utun-chatgui-ffmpeg-minimal-v4" case "$(uname -s)" in Linux) PLATFORM=linux ;; @@ -64,6 +64,7 @@ check_cmd nasm check_cmd pkg-config check_cmd git check_cmd curl +check_cmd patch # ============================================================================ # Step 1: Codec libraries (static .a, built with -fPIC) @@ -229,6 +230,11 @@ if [ ! -d "$FFMPEG_SRC" ]; then fi cd "$SRC_DIR/ffmpeg" +AMF_QP_PATCH="$SCRIPT_DIR/patches/ffmpeg-7.1.2-amf-live-qp.patch" +if ! grep -q 'utun_qp_applied' libavcodec/amfenc.c; then + log "Apply live AMF QP ceiling support..." + patch --batch --forward -p1 < "$AMF_QP_PATCH" || die "Failed to apply live AMF QP ceiling patch" +fi log "Configure FFmpeg..." CONFIGURE_ARGS=( diff --git a/tools/chatgui/tests/test_stream_publish.cpp b/tools/chatgui/tests/test_stream_publish.cpp index dc7530ac..8590ffd9 100644 --- a/tools/chatgui/tests/test_stream_publish.cpp +++ b/tools/chatgui/tests/test_stream_publish.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,7 @@ #include #endif #include "../src/live_video_encoder.h" +#include "../src/stream_qp_control.h" #include "../src/stream_publish_engine.h" #include "../src/stream_publish_window.h" #include "../src/sound_manager.h" @@ -257,6 +259,8 @@ private slots: boxes[1]->setCurrentIndex(boxes[1]->count() - 1); // Меньший размер сохраняется при повторном открытии. selectedMode = boxes[1]->currentData().toList(); boxes[3]->setCurrentIndex(boxes[3]->findData(4000000)); + controls.findChild("streamMaxQp")->setValue(25); + controls.findChild("streamAdaptiveQp")->setChecked(true); controls.findChild()->button(270)->click(); controls.findChildren().first()->setChecked(false); if (boxes[2]->count() > 1) boxes[2]->setCurrentIndex(boxes[2]->count() - 1); @@ -268,6 +272,7 @@ private slots: QCOMPARE(stored.value("bitrate").toInt(), 4000000); QCOMPARE(stored.value("rotation").toInt(), 270); QVERIFY(!stored.value("audio").toBool()); QVERIFY(!stored.value("show_cursor").toBool(true)); + QCOMPARE(stored.value("max_qp").toInt(), 25); QVERIFY(stored.value("adaptive_qp").toBool()); StreamPublishEngine reopenedEngine; StreamPublishWindow reopened(&reopenedEngine, group, nullptr, saved); QSignalSpy changes(&reopened, &StreamPublishWindow::settingsChanged); @@ -275,6 +280,8 @@ private slots: auto boxes = reopened.findChildren(); QCOMPARE(boxes[1]->currentData().toList(), selectedMode); QCOMPARE(boxes[3]->currentData().toInt(), 4000000); + QCOMPARE(reopened.findChild("streamMaxQp")->value(), 25); + QVERIFY(reopened.findChild("streamAdaptiveQp")->isChecked()); if (StreamPublishEngine::supported()) QTRY_COMPARE(boxes[2]->currentData().toInt(), stored.value("codec").toInt()); QCOMPARE(reopened.findChild()->checkedId(), 270); QVERIFY(!reopened.findChildren().first()->isChecked()); @@ -287,6 +294,21 @@ private slots: StreamPublishWindow restoredAuto(&reopenedEngine, group, nullptr, automatic); QCOMPARE(restoredAuto.findChildren()[3]->currentIndex(), 0); } + void qpAdaptationFollowsBitrateWithoutSpikes() { + StreamQpControl qp; qp.reset(28, 1000000, true, 0); + QCOMPARE(qp.update(1000000, 1000000), 28); // До двух секунд одиночный KEY не меняет потолок. + QCOMPARE(qp.update(0, 2000000), 29); + QCOMPARE(qp.update(1000000, 4000000), 30); // Не более одного пункта за интервал. + for (int64_t now = 6000000; now <= 12000000; now += 2000000) qp.update(250000, now); + QCOMPARE(qp.limit(), 31); // Сглаживание не дёргает QP около целевого битрейта. + for (int64_t now = 14000000; now <= 30000000; now += 2000000) qp.update(10000, now); + QCOMPARE(qp.limit(), 28); // Возвращаем выбранный потолок, не снижаем его дальше. + qp.reset(28, 1000000, false, 0); QCOMPARE(qp.update(10000000, 2000000), 28); + qp.reset(50, 1000000, true, 0); + QCOMPARE(qp.update(1000000, 2000000), 51); QCOMPARE(qp.update(1000000, 4000000), 51); + qp.reset(24, 4000000, true, 10000000); + QCOMPARE(qp.update(1000, 11000000), 24); QCOMPARE(qp.bitrate(), uint64_t(0)); + } void proportionalScreenModes_data() { QTest::addColumn("source"); QTest::addColumn("maximum"); QTest::addColumn("reduced"); QTest::newRow("full-hd") << QSize(1920, 1080) << QSize(1920, 1080) << QSize(1280, 720);