Réalisez un arrière-plan virtuel, de la beauté et des effets sonores spatiaux dans la vidéo multijoueur Flutter

avant-propos

Dans le précédent "Appels vidéo multiples basés sur le SDK Acoustics Flutter" , nous avons parfaitement réalisé l'effet des appels vidéo multiplateformes et multi-personnes via le SDK Flutter + Acoustics , donc dans cet article nous allons avancer sur la base des exemples précédents Introduisez certaines fonctions d'effets spéciaux couramment utilisées, notamment l'arrière-plan virtuel, l'amélioration des couleurs, l'audio spatial et les fonctions de changement de son de base.

Cet article vous amène principalement à comprendre plusieurs implémentations pratiques d'API dans le SDK, qui sont relativement simples.

01 Arrière-plan virtuel

enableVirtualBackgroundL'arrière-plan virtuel est l'un des effets spéciaux les plus courants dans les visioconférences, et la prise en charge de l'arrière-plan virtuel peut être activée via des méthodes dans le SDK Agora . ( Cliquez ici pour voir la documentation de l'interface d'arrière-plan virtuel ).

Tout d'abord, parce que nous l'utilisons dans Flutter, nous pouvons mettre une image dans Flutter assets/bg.jpgen arrière-plan, voici deux points à noter :

  • assets/bg.jpgL'image doit ajouter une référence pubspec.yamlsous le fichierassets
  assets:
    - assets/bg.jpg
  • Besoin d' pubspec.yamlajouter path_provider: ^2.0.8et path: ^1.8.2de dépendre du fichier, car nous devons enregistrer l'image dans le chemin local de l'application

rootBundleComme indiqué dans le code suivant, nous le lisons d'abord dans Flutter bg.jpg, puis le convertissons en bytes, puis appelons pour getApplicationDocumentsDirectoryobtenir le chemin, l'enregistrons dans le répertoire de l'application /data", puis configurons le chemin de l'image vers enableVirtualBackgroundla méthode sourcepour charger le virtuel arrière-plan.

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(() {});
}

Comme le montre la figure ci-dessous, il s'agit de l'effet de l'opération après l'activation de l'image d'arrière-plan virtuelle. Bien sûr, il y a deux paramètres qui nécessitent une attention :

  • BackgroundSourceType: Vous pouvez configurer backgroundColor(couleur d'arrière-plan virtuelle), backgroundImg(image d'arrière-plan virtuelle), backgroundBlur(flou d'arrière-plan virtuel), ces trois situations peuvent en principe couvrir toutes les scènes de la visioconférence
  • SegModelType: Il peut être configuré comme algorithme de matage dans deux scénarios différents segModelAi(algorithme intelligent) ou (algorithme d'écran vert).segModelGreen

Ce qu'il faut noter ici, c'est que dans l'invite officielle, il est recommandé d'utiliser cette fonction uniquement sur les appareils équipés des puces suivantes (devrait être nécessaire pour le GPU) :

  • Snapdragon série 700 750G et supérieur
  • Snapdragon série 800 835 et supérieur
  • Dimensity 700 série 720 et plus
  • Kirin 800 série 810 et supérieur
  • Kirin 900 série 980 et supérieur

De plus, il est à noter qu'afin d'adapter la résolution de l'image de fond personnalisée à la résolution de capture vidéo du SDK, le SDK SoundNet redimensionnera et recadrera l'image de fond personnalisée sans la déformer.

02 Beauté

L'embellissement est une autre fonction la plus couramment utilisée dans les visioconférences, et Shengwang fournit également setBeautyEffectOptionsdes méthodes pour prendre en charge certains ajustements de base de l'effet d'embellissement. ( Cliquez pour voir le document de l'interface beauté ).

Comme indiqué dans le code ci-dessous, setBeautyEffectOptionsla méthode consiste principalement à BeautyOptionsajuster le style de beauté de l'écran via la méthode, et les fonctions spécifiques des paramètres sont indiquées dans le tableau ci-dessous.

Le .5 ici n'est qu'un effet de démonstration. Plus précisément, vous pouvez configurer plusieurs modèles fixes que les utilisateurs pourront choisir en fonction des exigences de votre produit.

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

L'effet après la course est illustré dans la figure ci-dessous. Après avoir activé le paramètre 0,5, l'image globale de l'embellissement est plus juste et la couleur des lèvres est également plus évidente.

pas de beauté beauté ouverte

03 Amélioration des couleurs

La prochaine API à introduire est l'amélioration des couleurs : setColorEnhanceOptions, si la beauté n'est pas suffisante pour répondre à vos besoins, l'API d'amélioration des couleurs peut fournir plus de paramètres pour ajuster le style d'image dont vous avez besoin. ( Cliquez pour voir le document de l'interface d'amélioration des couleurs )

Comme le montre le code suivant, l'API d'amélioration des couleurs est très simple, ajustant principalement les paramètres ColorEnhanceOptionsl strengthLeveet skinProtectLevel, c'est-à-dire l'ajustement de l'effet de l'intensité de la couleur et de la protection de la couleur de la peau.

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

Comme le montre la figure ci-dessous, parce que les images vidéo capturées par la caméra peuvent avoir une distorsion des couleurs, et la fonction d'amélioration des couleurs peut ajuster intelligemment les caractéristiques vidéo telles que la saturation et le contraste pour améliorer la richesse des couleurs vidéo et la reproduction des couleurs, et enfin rendre l'image vidéo plus vif.

Après avoir activé l'amélioration, l'image est plus accrocheuse.

pas d'amélioration Activer Beauté + Amélioration

04 Effets sonores spatiaux

En fait, le réglage du son est le point culminant.Puisque SoundNet s'appelle SoundNet, il ne doit pas être à la traîne dans le traitement audio.Dans le SDK SoundNet, vous pouvez ouvrir l' enableSpatialAudioeffet des effets sonores spatiaux. ( Cliquez pour voir la documentation de l'interface audio spatiale )

_engine.enableSpatialAudio(true);

Qu'est-ce que le son spatial ? En termes simples, il s'agit d'un effet sonore 3D spécial , qui peut virtualiser la source sonore à émettre à partir d'une position spécifique dans un espace tridimensionnel, y compris le plan horizontal de l'auditeur, avant, arrière, gauche et droite, et verticalement au-dessus ou en dessous. .

Essentiellement, les effets sonores spatiaux sont calculés par certains algorithmes liés à l'acoustique pour simuler la réalisation d'effets sonores similaires aux effets 3D spatiaux.

Dans le même temps, vous pouvez également configurer les paramètres pertinents de l'effet sonore spatial, comme indiqué dans le tableau ci-dessous, vous pouvez voir que le réseau sonore fournit un ensemble de paramètres très riche pour nous permettre d'ajuster l'effet sonore spatial indépendamment, par exemple, l' setRemoteUserSpatialAudioParamseffet de somme ici est très intéressant et fortement recommandé. Tout le monde va essayer.enable_blurenable_air_absorb

Les effets audio ne peuvent pas être affichés ici, et je vous recommande fortement de l'essayer vous-même.

05 effets vocaux

Une autre API recommandée est l'effet de voix humaine : setAudioEffectPreset, appelez cette méthode pour modifier la voix de l'utilisateur sans changer les caractéristiques de genre de la voix d'origine grâce à l'effet de voix humaine prédéfini du SDK (cliquez pour afficher le document d'interface d'effet de voix humaine

_engine.setAudioEffectPreset(AudioEffectPreset.roomAcousticsKtv);

Il existe de nombreux préréglages dans le SDK SoundNet AudioEffectPreset, comme indiqué dans le tableau ci-dessous, des effets de scène tels que KTV, studio d'enregistrement, au changement de voix masculine et féminine, aux effets sonores usurpés tels que Zhu Bajie, qui peut être considéré comme assez incroyable.

PS : Afin d'obtenir de meilleurs effets vocaux, vous devez définir setAudioProfile scenariosur :

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

Bien sûr, ce qu'il faut noter ici, c'est que cette méthode n'est recommandée que pour le traitement des voix humaines, et n'est pas recommandée pour le traitement de données audio contenant de la musique.

Enfin, le code complet ressemble à ceci :

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 dernier

Le contenu de cet article est un supplément à "Appels vidéo multiples basés sur le SDK Acoustics Flutter" . Le contenu est relativement simple, mais on peut voir que le SDK Acoustics fournit une implémentation API très pratique, en particulier dans le traitement du son. Parce que le article est limité, seule une simple introduction à l'API est présentée ici, il est donc fortement recommandé d'essayer vous-même ces API audio. C'est vraiment intéressant. De plus, il existe de nombreuses scènes et gameplay, vous pouvez cliquer ici pour visiter le site officiel pour en savoir plus.

Les développeurs sont invités à essayer également le SDK SoundNet pour réaliser des scénarios d'interaction audio et vidéo en temps réel. Créez maintenant un compte Shengwang pour télécharger le SDK et vous pourrez obtenir un quota d'utilisation gratuit de 10 000 minutes par mois. Si vous avez des questions pendant le processus de développement, vous pouvez communiquer avec les ingénieurs officiels de la communauté des développeurs Shengwang .

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

Je suppose que tu aimes

Origine my.oschina.net/agora/blog/8591407
conseillé
Classement