Realice efectos de sonido espacial, de belleza y de fondo virtual en el video multijugador de Flutter

prefacio

En el anterior "Múltiples videollamadas basadas en el SDK de Acoustics Flutter" , nos dimos cuenta perfectamente del efecto de las videollamadas multiplataforma y multipersona a través de Flutter + Acoustics SDK , por lo que en este artículo avanzaremos en base a los ejemplos anteriores. Presente algunas funciones de efectos especiales de uso común, incluido el fondo virtual, la mejora del color, el audio espacial y las funciones básicas de cambio de sonido.

Este artículo lo lleva principalmente a comprender varias implementaciones prácticas de API en el SDK, que son relativamente simples.

01 Fondo virtual

enableVirtualBackgroundEl fondo virtual es uno de los efectos especiales más comunes en las videoconferencias, y la compatibilidad con el fondo virtual se puede habilitar a través de métodos en Agora SDK . ( Haga clic aquí para ver la documentación de la interfaz de fondo virtual ).

En primer lugar, debido a que lo estamos usando en Flutter, podemos poner una imagen en Flutter assets/bg.jpgcomo fondo, aquí hay dos puntos a tener en cuenta:

  • assets/bg.jpgLa imagen necesita agregar una referencia pubspec.yamldebajo del archivo.assets
  assets:
    - assets/bg.jpg
  • Necesita pubspec.yamlagregar path_provider: ^2.0.8y path: ^1.8.2depender del archivo, porque necesitamos guardar la imagen en la ruta local de la aplicación

rootBundleComo se muestra en el siguiente código, primero lo leemos en Flutter bg.jpg, luego lo convertimos a bytes, y luego llamamos para getApplicationDocumentsDirectoryobtener la ruta, la guardamos en el directorio de la aplicación /data"y luego configuramos la ruta de la imagen al enableVirtualBackgroundmétodo sourcepara cargar el virtual. fondo.

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

Como se muestra en la figura a continuación, es el efecto de la operación después de que se enciende la imagen de fondo virtual. Por supuesto, hay dos parámetros que necesitan atención:

  • BackgroundSourceType: Puede configurar backgroundColor(color de fondo virtual), backgroundImg(imagen de fondo virtual), backgroundBlur(desenfoque de fondo virtual), estas tres situaciones pueden cubrir básicamente todas las escenas de la videoconferencia
  • SegModelType: Se puede configurar como un algoritmo de matización en dos escenarios diferentes segModelAi(algoritmo inteligente) o (algoritmo de pantalla verde).segModelGreen

Lo que debe tenerse en cuenta aquí es que en el indicador oficial, se recomienda usar esta función solo en dispositivos equipados con los siguientes chips (deben ser necesarios para la GPU):

  • Snapdragon 700 serie 750G y superior
  • Snapdragon 800 serie 835 y superior
  • Dimensión 700 serie 720 y superior
  • Kirin 800 serie 810 y superior
  • Kirin 900 serie 980 y superior

Además, cabe señalar que para adaptar la resolución de la imagen de fondo personalizada a la resolución de captura de vídeo del SDK, SoundNet SDK escalará y recortará la imagen de fondo personalizada sin deformarla.

02 belleza

El embellecimiento es otra de las funciones más utilizadas en las videoconferencias, y Shengwang también proporciona setBeautyEffectOptionsmétodos para admitir algunos ajustes básicos del efecto de embellecimiento. ( Haga clic para ver el documento de la interfaz de belleza ).

Como se muestra en el código a continuación, setBeautyEffectOptionsel método es principalmente para BeautyOptionsajustar el estilo de belleza de la pantalla a través del método, y las funciones específicas de los parámetros se muestran en la tabla a continuación.

El .5 aquí es solo un efecto de demostración. Específicamente, puede configurar varias plantillas fijas para que los usuarios elijan de acuerdo con los requisitos de su producto.

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

El efecto después de correr se muestra en la siguiente figura. Después de activar el parámetro 0.5, la imagen general del embellecimiento es más justa y el color de los labios también es más obvio.

sin belleza belleza abierta

03 Mejora del color

La siguiente API que se presentará es la mejora del color: setColorEnhanceOptionssi la belleza no es suficiente para satisfacer sus necesidades, la API de mejora del color puede proporcionar más parámetros para ajustar el estilo de imagen que necesita. ( Haga clic para ver el documento de la interfaz de mejora del color )

Como se muestra en el siguiente código, la API de mejora del color es muy simple, principalmente ajusta los parámetros ColorEnhanceOptionsl strengthLevey skinProtectLevel, es decir, ajusta el efecto de la intensidad del color y la protección del color de la piel.

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

Como se muestra en la figura a continuación, debido a que las imágenes de video capturadas por la cámara pueden tener distorsión de color, y la función de mejora de color puede ajustar de manera inteligente las características del video, como la saturación y el contraste, para mejorar la riqueza y reproducción del color del video, y finalmente hacer que la imagen de video más vivo

Después de activar la mejora, la imagen es más llamativa.

sin mejora Activa Belleza + Realce

04 Efectos de sonido espaciales

De hecho, el ajuste de sonido es lo más destacado. Dado que SoundNet se llama SoundNet, no debe quedarse atrás en el procesamiento de audio. En SoundNet SDK, puede abrir el enableSpatialAudioefecto de los efectos de sonido espacial. ( Haga clic para ver la documentación de la interfaz de audio espacial )

_engine.enableSpatialAudio(true);

¿Qué es el sonido espacial? En pocas palabras, es un efecto de sonido 3D especial , que puede virtualizar la fuente de sonido que se emitirá desde una posición específica en un espacio tridimensional, incluido el plano horizontal del oyente, adelante, atrás, izquierda y derecha, y verticalmente arriba o abajo. .

En esencia, los efectos de sonido espacial se calculan mediante algunos algoritmos relacionados con la acústica para simular la realización de efectos de sonido similares a los efectos 3D espaciales.

Al mismo tiempo, también puede configurar los parámetros relevantes del efecto de sonido espacial, como se muestra en la tabla a continuación, puede ver que la red de sonido proporciona un conjunto muy rico de parámetros que nos permiten ajustar el efecto de sonido espacial de forma independiente, por ejemplo, el setRemoteUserSpatialAudioParamsefecto de suma aquí es muy interesante y muy recomendable. Todos vayan y prueben.enable_blurenable_air_absorb

Los efectos de audio no se pueden mostrar aquí, y le recomiendo que lo pruebe usted mismo.

05 efectos vocales

Otra API recomendada es el efecto de voz humana: setAudioEffectPresetllame a este método para modificar la voz del usuario sin cambiar las características de género de la voz original a través del efecto de voz humana preestablecido del SDK (haga clic para ver el documento de la interfaz del efecto de voz humana

_engine.setAudioEffectPreset(AudioEffectPreset.roomAcousticsKtv);

Hay muchos ajustes preestablecidos en SoundNet SDK AudioEffectPreset, como se muestra en la tabla a continuación, desde efectos de escena como KTV, estudio de grabación, cambio de voz masculina y femenina, hasta efectos de sonido falsos como Zhu Bajie, que se puede decir que es bastante sorprendente.

PD: para obtener mejores efectos vocales, debe configurar setAudioProfile scenarioen :

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

Por supuesto, lo que debe tenerse en cuenta aquí es que este método solo se recomienda para procesar voces humanas y no se recomienda para procesar datos de audio que contengan música.

Finalmente, el código completo se ve así:

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 último

El contenido de este artículo es un complemento de "Múltiples videollamadas basadas en Acoustics Flutter SDK" . El contenido es relativamente simple, pero se puede ver que Acoustics SDK proporciona una implementación de API muy conveniente, especialmente en el procesamiento de sonido. Debido a que el El artículo es limitado, aquí solo se muestra una introducción simple de la API, por lo que se recomienda encarecidamente que pruebe estas API de audio usted mismo. Es realmente interesante. Además, hay muchas escenas y juegos, puede hacer clic aquí para visitar el sitio web oficial para obtener más información.

Los desarrolladores también pueden probar SoundNet SDK para realizar escenarios de interacción de audio y video en tiempo real. Ahora regístrese para obtener una cuenta de Shengwang para descargar el SDK y podrá obtener una cuota de uso gratuita de 10 000 minutos al mes. Si tiene alguna pregunta durante el proceso de desarrollo, puede comunicarse con los ingenieros oficiales en la comunidad de desarrolladores de Shengwang .

{{o.nombre}}
{{m.nombre}}

Supongo que te gusta

Origin my.oschina.net/agora/blog/8591407
Recomendado
Clasificación