Realisieren Sie virtuelle Hintergründe, Schönheit und räumliche Soundeffekte in Flutter-Multiplayer-Videos

Vorwort

Im vorherigen „Mehrere Videoanrufe basierend auf dem Acoustics Flutter SDK“ haben wir die Wirkung von plattformübergreifenden und Mehrpersonen-Videoanrufen durch Flutter + Acoustics SDK perfekt realisiert , daher werden wir in diesem Artikel auf der Grundlage der vorherigen Beispiele fortfahren Stellen Sie einige häufig verwendete Spezialeffektfunktionen vor, darunter virtueller Hintergrund, Farbverbesserung, räumliches Audio und grundlegende Klangänderungsfunktionen.

Dieser Artikel führt Sie hauptsächlich dazu, einige praktische API-Implementierungen im SDK zu verstehen, die relativ einfach sind.

01 Virtueller Hintergrund

enableVirtualBackgroundDer virtuelle Hintergrund ist einer der häufigsten Spezialeffekte bei Videokonferenzen, und die Unterstützung des virtuellen Hintergrunds kann durch Methoden im Agora SDK aktiviert werden . ( Klicken Sie hier, um die Dokumentation der virtuellen Hintergrundschnittstelle anzuzeigen ).

Da wir es in Flutter verwenden, können wir zunächst ein Bild assets/bg.jpgals Hintergrund in Flutter einfügen, hier sind zwei Punkte zu beachten:

  • assets/bg.jpgDas Bild muss einen Verweis pubspec.yamlunter der Datei hinzufügenassets
  assets:
    - assets/bg.jpg
  • Muss pubspec.yamlhinzugefügt werden path_provider: ^2.0.8und path: ^1.8.2hängt von der Datei ab, da wir das Bild im lokalen Pfad der App speichern müssen

rootBundleWie im folgenden Code gezeigt, lesen wir es zuerst in Flutter bg.jpgund konvertieren es dann in bytes, rufen dann auf, um getApplicationDocumentsDirectoryden Pfad zu erhalten, speichern es im Verzeichnis der Anwendung /data"und konfigurieren dann den Bildpfad für enableVirtualBackgrounddie Methode sourcezum Laden des virtuellen Hintergrund.

Future<void> _enableVirtualBackground() async {
  ByteData data = await rootBundle.load("assets/bg.jpg");
  List<int> bytes =
      data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
  Directory appDocDir = await getApplicationDocumentsDirectory();
  String p = path.join(appDocDir.path, 'bg.jpg');
  final file = File(p);
  if (!(await file.exists())) {
    await file.create();
    await file.writeAsBytes(bytes);
  }

  await _engine.enableVirtualBackground(
      enabled: true,
      backgroundSource: VirtualBackgroundSource(
          backgroundSourceType: BackgroundSourceType.backgroundImg,
          source: p),
      segproperty:
          const SegmentationProperty(modelType: SegModelType.segModelAi));
  setState(() {});
}

Wie in der Abbildung unten gezeigt, stellt dies den Betriebseffekt nach dem Einschalten des virtuellen Hintergrundbilds dar. Natürlich gibt es zwei Parameter, die beachtet werden müssen:

  • BackgroundSourceTypebackgroundColor: Sie können (virtuelle Hintergrundfarbe), backgroundImg(virtuelles Hintergrundbild), backgroundBlur(virtuelle Hintergrundunschärfe) konfigurieren , diese drei Situationen können im Grunde alle Szenen in der Videokonferenz abdecken
  • SegModelType: Es kann als Matting-Algorithmus in zwei verschiedenen Szenarien segModelAi(Smart-Algorithmus) oder (Green-Screen-Algorithmus) konfiguriert werden .segModelGreen

Zu beachten ist hier, dass in der offiziellen Aufforderung empfohlen wird, diese Funktion nur auf Geräten zu verwenden, die mit folgenden Chips ausgestattet sind (sollte für GPU erforderlich sein):

  • Snapdragon 700-Serie 750G und höher
  • Snapdragon 800 Serie 835 und höher
  • Abmessung 700 Serie 720 und höher
  • Kirin 800 Serie 810 und höher
  • Kirin 900 Serie 980 und höher

Darüber hinaus sollte beachtet werden, dass das SoundNet SDK das benutzerdefinierte Hintergrundbild skaliert und zuschneidet, ohne es zu verformen, um die Auflösung des benutzerdefinierten Hintergrundbilds an die Videoaufnahmeauflösung des SDK anzupassen.

02 Schönheit

Die Verschönerung ist eine weitere am häufigsten verwendete Funktion bei Videokonferenzen, und Shengwang bietet auch setBeautyEffectOptionsMethoden zur Unterstützung einiger grundlegender Anpassungen des Verschönerungseffekts. ( Klicken Sie hier, um das Beauty-Interface-Dokument anzuzeigen ).

Wie im folgenden Code gezeigt, setBeautyEffectOptionsbesteht die Methode hauptsächlich darin, BeautyOptionsden Schönheitsstil des Bildschirms durch die Methode anzupassen, und die spezifischen Funktionen der Parameter sind in der folgenden Tabelle gezeigt.

Die .5 hier stellt nur einen Demo-Effekt dar. Insbesondere können Sie mehrere feste Vorlagen konfigurieren, die Benutzer gemäß Ihren Produktanforderungen auswählen können.

_engine.setBeautyEffectOptions(
  enabled: true,
  options: const BeautyOptions(
    lighteningContrastLevel:
        LighteningContrastLevel.lighteningContrastHigh,
    lighteningLevel: .5,
    smoothnessLevel: .5,
    rednessLevel: .5,
    sharpnessLevel: .5,
  ),
);

Der Effekt nach dem Laufen ist in der folgenden Abbildung dargestellt: Nach dem Einschalten des Parameters 0,5 ist das Gesamtbild der Verschönerung fairer und auch die Lippenfarbe ist deutlicher.

keine Schönheit offene Schönheit

03 Farbverbesserung

Die nächste API, die eingeführt wird, ist die Farbverbesserung: setColorEnhanceOptionsWenn die Schönheit nicht ausreicht, um Ihre Anforderungen zu erfüllen, kann die Farbverbesserungs-API weitere Parameter bereitstellen, um den gewünschten Bildstil anzupassen. ( Klicken Sie hier, um das Dokument zur Farbverbesserungsschnittstelle anzuzeigen .)

Wie im folgenden Code gezeigt, ist die Farbverbesserungs-API sehr einfach und passt hauptsächlich ColorEnhanceOptionsl strengthLeveund skinProtectLevelParameter an, d. h. die Anpassung der Wirkung der Farbintensität und des Schutzes der Hautfarbe.

  _engine.setColorEnhanceOptions(
      enabled: true,
      options: const ColorEnhanceOptions(
          strengthLevel: 6.0, skinProtectLevel: 0.7));

Wie in der Abbildung unten gezeigt, da die von der Kamera aufgenommenen Videobilder Farbverzerrungen aufweisen können und die Farbverbesserungsfunktion Videoeigenschaften wie Sättigung und Kontrast intelligent anpassen kann, um den Farbreichtum und die Farbwiedergabe des Videos zu verbessern und schließlich das Videobild zu erstellen lebendiger.

Nach dem Einschalten der Verbesserung ist das Bild auffälliger.

keine Steigerung Schalten Sie Schönheit + Verbesserung ein

04 Räumliche Soundeffekte

Tatsächlich stellt das Soundtuning das Highlight dar. Da SoundNet SoundNet heißt, darf es bei der Audiobearbeitung nicht hinterherhinken.Im SoundNet SDK können Sie die enableSpatialAudioWirkung räumlicher Soundeffekte öffnen. ( Klicken Sie hier, um die Dokumentation zum räumlichen Audio-Interface anzuzeigen .)

_engine.enableSpatialAudio(true);

Was ist Raumklang? Einfach ausgedrückt handelt es sich um einen speziellen 3D-Soundeffekt , der die Schallquelle virtualisieren kann, die von einer bestimmten Position im dreidimensionalen Raum ausgeht, einschließlich der horizontalen Ebene des Zuhörers, vorne, hinten, links und rechts sowie vertikal darüber oder darunter .

Im Wesentlichen werden räumliche Klangeffekte durch einige akustikbezogene Algorithmen berechnet, um die Realisierung von Klangeffekten ähnlich wie räumliche 3D-Effekte zu simulieren.

Gleichzeitig können Sie auch die relevanten Parameter des räumlichen Soundeffekts konfigurieren, wie in der folgenden Tabelle gezeigt. Sie können sehen, dass das Soundnetzwerk einen sehr reichhaltigen Satz von Parametern bietet, mit denen wir den räumlichen Soundeffekt unabhängig anpassen können. Zum Beispiel ist der Summeneffekt setRemoteUserSpatialAudioParamshier sehr interessant und sehr zu empfehlen. Gehen Sie hin und probieren Sie es aus.enable_blurenable_air_absorb

Audioeffekte können hier nicht angezeigt werden, und ich empfehle dringend, dass Sie es selbst versuchen.

05 Gesangseffekte

Eine weitere empfohlene API ist der Human-Voice-Effekt: setAudioEffectPresetRufen Sie diese Methode auf, um die Stimme des Benutzers zu ändern, ohne die Geschlechtsmerkmale der Originalstimme durch den voreingestellten Human-Voice-Effekt des SDK zu ändern (klicken Sie hier, um das Interface-Dokument zum Human-Voice-Effekt anzuzeigen

_engine.setAudioEffectPreset(AudioEffectPreset.roomAcousticsKtv);

Wie in der folgenden Tabelle gezeigt, gibt es im SoundNet SDK viele Voreinstellungen AudioEffectPreset, von Szeneneffekten wie KTV, Aufnahmestudio über die Änderung der männlichen und weiblichen Stimme bis hin zu gefälschten Soundeffekten wie Zhu Bajie, von denen man sagen kann, dass sie es sind schon erstaunlich.

PS: Um bessere Stimmeffekte zu erzielen, müssen Sie setAudioProfile scenarioauf :

_engine.setAudioProfile(
  profile: AudioProfileType.audioProfileDefault,
  scenario: AudioScenarioType.audioScenarioGameStreaming);

An dieser Stelle muss natürlich angemerkt werden, dass dieses Verfahren nur für die Verarbeitung menschlicher Stimmen empfohlen wird und nicht für die Verarbeitung von Audiodaten, die Musik enthalten.

Schließlich sieht der vollständige Code so aus:

class VideoChatPage extends StatefulWidget {
  const VideoChatPage({Key? key}) : super(key: key);

  @override
  State<VideoChatPage> createState() => _VideoChatPageState();
}

class _VideoChatPageState extends State<VideoChatPage> {
  late final RtcEngine _engine;

  ///初始化状态
  late final Future<bool?> initStatus;

  ///当前 controller
  late VideoViewController currentController;

  ///是否加入聊天
  bool isJoined = false;

  /// 记录加入的用户id
  Map<int, VideoViewController> remoteControllers = {};

  @override
  void initState() {
    super.initState();
    initStatus = _requestPermissionIfNeed().then((value) async {
      await _initEngine();

      ///构建当前用户 currentController
      currentController = VideoViewController(
        rtcEngine: _engine,
        canvas: const VideoCanvas(uid: 0),
      );
      return true;
    }).whenComplete(() => setState(() {}));
  }

  Future<void> _requestPermissionIfNeed() async {
    if (Platform.isMacOS) {
      return;
    }
    await [Permission.microphone, Permission.camera].request();
  }

  Future<void> _initEngine() async {
    //创建 RtcEngine
    _engine = createAgoraRtcEngine();
    // 初始化 RtcEngine
    await _engine.initialize(const RtcEngineContext(
      appId: appId,
    ));

    _engine.registerEventHandler(RtcEngineEventHandler(
      // 遇到错误
      onError: (ErrorCodeType err, String msg) {
        if (kDebugMode) {
          print('[onError] err: $err, msg: $msg');
        }
      },
      onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
        // 加入频道成功
        setState(() {
          isJoined = true;
        });
      },
      onUserJoined: (RtcConnection connection, int rUid, int elapsed) {
        // 有用户加入
        setState(() {
          remoteControllers[rUid] = VideoViewController.remote(
            rtcEngine: _engine,
            canvas: VideoCanvas(uid: rUid),
            connection: const RtcConnection(channelId: cid),
          );
        });
      },
      onUserOffline:
          (RtcConnection connection, int rUid, UserOfflineReasonType reason) {
        // 有用户离线
        setState(() {
          remoteControllers.remove(rUid);
        });
      },
      onLeaveChannel: (RtcConnection connection, RtcStats stats) {
        // 离开频道
        setState(() {
          isJoined = false;
          remoteControllers.clear();
        });
      },
    ));

    // 打开视频模块支持
    await _engine.enableVideo();
    // 配置视频编码器,编码视频的尺寸(像素),帧率
    await _engine.setVideoEncoderConfiguration(
      const VideoEncoderConfiguration(
        dimensions: VideoDimensions(width: 640, height: 360),
        frameRate: 15,
      ),
    );

    await _engine.startPreview();
  }

  @override
  void dispose() {
    _engine.leaveChannel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(),
        body: Stack(
          children: [
            FutureBuilder<bool?>(
                future: initStatus,
                builder: (context, snap) {
                  if (snap.data != true) {
                    return const Center(
                      child: Text(
                        "初始化ing",
                        style: TextStyle(fontSize: 30),
                      ),
                    );
                  }
                  return AgoraVideoView(
                    controller: currentController,
                  );
                }),
            Align(
              alignment: Alignment.topLeft,
              child: SingleChildScrollView(
                scrollDirection: Axis.horizontal,
                child: Row(
                  ///增加点击切换
                  children: List.of(remoteControllers.entries.map(
                    (e) => InkWell(
                      onTap: () {
                        setState(() {
                          remoteControllers[e.key] = currentController;
                          currentController = e.value;
                        });
                      },
                      child: SizedBox(
                        width: 120,
                        height: 120,
                        child: AgoraVideoView(
                          controller: e.value,
                        ),
                      ),
                    ),
                  )),
                ),
              ),
            )
          ],
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () async {
            // 加入频道
            _engine.joinChannel(
              token: token,
              channelId: cid,
              uid: 0,
              options: const ChannelMediaOptions(
                channelProfile:
                    ChannelProfileType.channelProfileLiveBroadcasting,
                clientRoleType: ClientRoleType.clientRoleBroadcaster,
              ),
            );
          },
        ),
        persistentFooterButtons: [
          ElevatedButton.icon(
              onPressed: () {
                _enableVirtualBackground();
              },
              icon: const Icon(Icons.accessibility_rounded),
              label: const Text("虚拟背景")),
          ElevatedButton.icon(
              onPressed: () {
                _engine.setBeautyEffectOptions(
                  enabled: true,
                  options: const BeautyOptions(
                    lighteningContrastLevel:
                        LighteningContrastLevel.lighteningContrastHigh,
                    lighteningLevel: .5,
                    smoothnessLevel: .5,
                    rednessLevel: .5,
                    sharpnessLevel: .5,
                  ),
                );
                //_engine.setRemoteUserSpatialAudioParams();
              },
              icon: const Icon(Icons.face),
              label: const Text("美颜")),
          ElevatedButton.icon(
              onPressed: () {
                _engine.setColorEnhanceOptions(
                    enabled: true,
                    options: const ColorEnhanceOptions(
                        strengthLevel: 6.0, skinProtectLevel: 0.7));
              },
              icon: const Icon(Icons.color_lens),
              label: const Text("增强色彩")),
          ElevatedButton.icon(
              onPressed: () {
                _engine.enableSpatialAudio(true);
              },
              icon: const Icon(Icons.surround_sound),
              label: const Text("空间音效")),
          ElevatedButton.icon(
              onPressed: () {                
                _engine.setAudioProfile(
                    profile: AudioProfileType.audioProfileDefault,
                    scenario: AudioScenarioType.audioScenarioGameStreaming);
                _engine
                    .setAudioEffectPreset(AudioEffectPreset.roomAcousticsKtv);
              },
              icon: const Icon(Icons.surround_sound),
              label: const Text("人声音效")),
        ]);
  }

  Future<void> _enableVirtualBackground() async {
    ByteData data = await rootBundle.load("assets/bg.jpg");
    List<int> bytes =
        data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
    Directory appDocDir = await getApplicationDocumentsDirectory();
    String p = path.join(appDocDir.path, 'bg.jpg');
    final file = File(p);
    if (!(await file.exists())) {
      await file.create();
      await file.writeAsBytes(bytes);
    }

    await _engine.enableVirtualBackground(
        enabled: true,
        backgroundSource: VirtualBackgroundSource(
            backgroundSourceType: BackgroundSourceType.backgroundImg,
            source: p),
        segproperty:
            const SegmentationProperty(modelType: SegModelType.segModelAi));
    setState(() {});
  }
}

06 zuletzt

Der Inhalt dieses Artikels stellt eine Ergänzung zu "Multiple Video Calls Based on the Acoustics Flutter SDK" dar. Der Inhalt ist relativ einfach, aber es ist zu erkennen, dass das Acoustics SDK eine sehr komfortable API-Implementierung bietet, insbesondere bei der Tonverarbeitung Artikel ist begrenzt, hier wird nur eine einfache API-Einführung gezeigt, daher wird dringend empfohlen, dass Sie diese Audio-APIs selbst ausprobieren. Es ist wirklich interessant. Darüber hinaus gibt es viele Szenen und Gameplay, Sie können hier klicken , um die offizielle Website zu besuchen und mehr zu erfahren.

Entwickler können gerne auch das SoundNet SDK ausprobieren, um Audio- und Video-Interaktionsszenarien in Echtzeit zu realisieren. Registrieren Sie sich jetzt für ein Shengwang-Konto, um das SDK herunterzuladen , und Sie können ein kostenloses Nutzungskontingent von 10.000 Minuten pro Monat erhalten. Wenn Sie während des Entwicklungsprozesses Fragen haben, können Sie mit offiziellen Ingenieuren in der Shengwang-Entwickler-Community kommunizieren.

{{o.name}}
{{m.name}}

Ich denke du magst

Origin my.oschina.net/agora/blog/8591407
Empfohlen
Rangfolge