Flutter는 FFI+CustomPainter를 사용하여 전체 플랫폼 렌더링 비디오를 구현합니다.

Flutter 비디오 렌더링 시리즈

1장 Android는 Texture를 사용하여 비디오를 렌더링합니다
. 2장 Windows는 Texture를 사용하여 비디오를 렌더링합니다.
3장 Linux는 Texture를 사용하여 비디오를 렌더링합니다.
4장 전체 플랫폼 FFI+CustomPainter를 사용하여 비디오를 렌더링합니다(이 장).



머리말

이전 장에서는 Flutter가 텍스처를 사용하여 동영상을 렌더링하는 방법을 소개했지만 각 플랫폼에서 텍스처를 생성하기 위해 네이티브 코드 세트를 작성해야 하므로 코드 유지 관리에 도움이 되지 않는 문제가 있습니다. 가장 좋은 방법은 일련의 코드가 모든 플랫폼에서 실행될 수 있어야 하므로 C++를 사용하여 크로스 플랫폼 비디오 캡처를 구현하고 ffi를 통해 데이터를 dart 인터페이스로 전송하고 이미지를 그리는 아이디어를 생각해 냈습니다. 캔버스 컨트롤. 마지막으로 테스트를 통해 사용 가능한 솔루션은 CustomPainter와 결합하여 비디오 렌더링을 달성하는 것으로 나타났습니다.이러한 방식으로 구현된 비디오 렌더링은 모든 플랫폼(웹 제외)에서 일련의 코드를 실행할 수 있습니다 .


1. 달성 방법

1. C/C++는 비디오 프레임을 캡처합니다.

(1) C++ 코드 작성

플레이어는 비디오 컬렉션의 일종입니다. 예를 들어 다음 코드는 플레이어에 대한 간단한 정의입니다.
여기에 이미지 설명 삽입
ffplay.h의 예는 다음과 같습니다.

//播放回调方法原型
typedef void(*DisplayEventHandler)(void*play,unsigned char* data[8], int linesize[8], int width, int height, AVPixelFormat format);
//创建播放器
void*play_create();
//销毁播放器
void play_destory(void*);
//设置渲染回调
void play_setDisplayCallback(void*, DisplayEventHandler callback);
//开始播放(异步)
void play_start(void*,const char*);
//开始播放(同步)
void play_exec(void*, const char*);
//停止播放
void play_stop(void*);

(2) CMakeList 쓰기

각 플랫폼에 대한 cmake.

  • Windows 및 Linux용 CMakeList(일부)
# Project-level configuration.
set(PROJECT_NAME "ffplay_plugin")
project(${PROJECT_NAME} LANGUAGES CXX)

# This value is used when generating builds using this plugin, so it must
# not be changed.
set(PLUGIN_NAME "ffplay_plugin_plugin")

# Define the plugin library target. Its name must not be changed (see comment
# on PLUGIN_NAME above).
#
# Any new source files that you add to the plugin should be added here.
add_library(${PLUGIN_NAME} SHARED
  "ffplay_plugin.cc"
"../ffi/ffplay.cpp"
"../ffi/DllImportUtils.cpp"
)
target_link_libraries(${PLUGIN_NAME} PRIVATE flutter  )
  • Android의 jni CMakeList(일부)
add_library( # Sets the name of the library.
        ffplay_plugin_plugin
        # Sets the library as a shared library.
        SHARED
        # Provides a relative path to your source file(s).
        ../../../../ffi/ffplay.cpp
        ../../../../ffi/DllImportUtils.cpp
        )
target_link_libraries( # Specifies the target library.
                       ffplay_plugin_plugin
                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib}
                       android
                       )

2. FFI는 C/C++ 방식을 가져옵니다.

(1), 종속 패키지

import 'dart:ffi'; // For FFI
import 'package:ffi/ffi.dart';
import 'dart:io'; // For Platform.isX

(2) 동적 라이브러리 로드

다른 플랫폼에 따라 동적 라이브러리를 로드합니다. 일반적으로 Windows는 dll이고 다른 플랫폼도 마찬가지입니다. 동적 라이브러리의 이름은 위의 CMakeList에 의해 결정됩니다.

final DynamicLibrary nativeLib = Platform.isWindows
    ? DynamicLibrary.open("ffplay_plugin_plugin.dll")
    : DynamicLibrary.open("libffplay_plugin_plugin.so");

(3) 정의 방법

예를 들어, ffplay.h의 메서드는 다음과 같이 dart 정의에 해당합니다
.

//播放回调方法原型
typedef display_callback = Void Function(Pointer<Void>, Pointer<Pointer<Uint8>>,
    Pointer<Int32>, Int32, Int32, Int32);
//创建播放器
final Pointer<Void> Function() play_create = nativeLib
    .lookup<NativeFunction<Pointer<Void> Function()>>('play_create')
    .asFunction();
//销毁播放器
final void Function(Pointer<Void>) play_destory = nativeLib
    .lookup<NativeFunction<Void Function(Pointer<Void>)>>('play_destory')
    .asFunction();
//设置渲染回调
final void Function(Pointer<Void>, Pointer<NativeFunction<display_callback>>)
    play_setDisplayCallback = nativeLib
        .lookup<
                NativeFunction<
                    Void Function(Pointer<Void>,
                        Pointer<NativeFunction<display_callback>>)>>(
            'play_setDisplayCallback')
        .asFunction();
//开始播放(异步)
final void Function(Pointer<Void>, Pointer<Int8>) play_start = nativeLib
    .lookup<NativeFunction<Void Function(Pointer<Void>, Pointer<Int8>)>>(
        'play_start')
    .asFunction();
//开始播放(同步)
final void Function(Pointer<Void>, Pointer<Int8>) play_exec = nativeLib
    .lookup<NativeFunction<Void Function(Pointer<Void>, Pointer<Int8>)>>(
        'play_exec')
    .asFunction();
//停止播放
final void Function(Pointer<Void>) play_stop = nativeLib
    .lookup<NativeFunction<Void Function(Pointer<Void>)>>('play_stop')
    .asFunction();

3. 격리는 수집 스레드를 시작합니다.

Flutter의 인터페이스 메커니즘은 스레드 간의 데이터 공유를 허용하지 않으며 전역 변수는 모두 TLS이므로 C/C++에서 생성된 스레드는 렌더링을 위해 재생 데이터를 메인 스레드로 직접 전송할 수 없으므로 dart를 사용하여 C/용 Isolate를 생성해야 합니다. C++ 플레이어가 실행되고 데이터는 sendPort를 통해 메인 스레드로 전송됩니다.

(1) 입력 방법 정의

진입 방법은 자식 스레드 방법과 동일합니다.
메인 다트

//Isolate通信端口
SendPort? m_sendPort;
//Isolate入口方法
  static isolateEntry(SendPort sendPort) async {
    
    
    //记录sendPort
    m_sendPort = sendPort;
    //播放逻辑,此处需要堵塞,简单点可以在播放逻辑中堵塞,也可以放一个C/C++消息队列给多路流线程通信做调度。
    //比如采用播放逻辑阻塞实现,阻塞后在渲染回调方法中使用sendPort将视频数据发送到主线程,回调必须在此线程中。
     
    //发送消息通知结束播放
    sendPort?.send([1]);
  }

(2) 격리 생성

입력 방법을 사용하여 Isolate를 만들 수 있습니다. 예는 다음과 같습니다
.

  startPlay() async {
    
    
    ReceivePort receivePort = ReceivePort();
    //创建一个Isolate相当于创建一个子线程
    await Isolate.spawn(isolateEntry, receivePort.sendPort);
    // 监听Isolate子线程消息port
    await for (var msg in receivePort) {
    
    
      //处理Isolate子线程发过来的视频数据
      
      int type=msg[0];
      if(type==1)
      //结束播放
        break;
  }
}

4. 커스텀 페인터 그리기

(1), 커스텀 드로잉

커스텀 페인팅은 CustomPainter를 상속받아 페인트 메소드를 구현하고 페인트 메소드에서 ui.image를 그려야 합니다. 이 ui.image는 argb 데이터를 트랜스코딩하여 얻을 수 있습니다.
메인 다트

import 'dart:ui' as ui;
//渲染的image
ui.Image? image;
//通知控件绘制
ChangeNotifier notifier = ChangeNotifier();
//自定义panter
class MyCustomPainter extends CustomPainter {
    
    
  //触发绘制的标识
  ChangeNotifier flag;
  MyCustomPainter(this.flag) : super(repaint: flag);
  
  void paint(Canvas canvas, ui.Size size) {
    
    
    //绘制image
    if (image != null) canvas.drawImage(image!, Offset(0, 0), Paint());
  }
  
  bool shouldRepaint(MyCustomPainter oldDelegate) => true;
}

(2), 레이아웃 인터페이스

인터페이스에서 사용자 지정 CustomPainter를 사용하고 ChangeNotifier 개체를 전달하여 그리기를 트리거합니다.
메인 다트

  
  Widget build(BuildContext context) {
    
    
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      //控件布局
      body: Center(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Container(
              width: 640,
              height: 360,
              child: Center(
                child: CustomPaint(
                  foregroundPainter: MyCustomPainter(notifier),
                  child: Container(
                    width: 640,
                    height: 360,
                    color: Color(0x5a00C800),
                  ),
                ),
              ),
            )
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: onClick,
        tooltip: 'play or stop',
        child: Icon(Icons.add),
      ),
    );
  }

(3) 비디오 프레임 그리기

재생 데이터를 메인 스레드로 보낸 후 argb 데이터를 ui.image 객체로 변환해야 하며 ui.decodeImageFromPixels 메서드를 직접 사용할 수 있습니다.
메인 다트

 ui.decodeImageFromPixels(pixels, width, height, PixelFormat.rgba8888,
            (result) {
    
    
          image = result;
          //通知绘制
          notifier.notifyListeners();
        }, rowBytes: linesize, targetWidth: 640, targetHeight: 360);

2. 효과 미리보기

기본적인 달리기 효과
여기에 이미지 설명 삽입


3. 성능 비교

실제로 탐색 과정에서 RawImage 방식을 사용하여 동영상을 렌더링하여 화면을 성공적으로 출력하였지만 CPU 사용률이 매우 높아 실제 개발에는 사용할 수 없었다. 마지막으로 이 기사에서 이 방법의 성능이 실제로 그다지 좋지 않다는 것을 알았습니다 텍스처 렌더링과 비교할 때 여전히 약간의 차이가 있지만 사용할 수 있습니다.
테스트 플랫폼: Windows 11
테스트 장비: i7 8750h gpu 사용 핵 디스플레이
데이터 기록: 30초 이내에 5회 수행하여 평균 계산

이 기사는 렌더링

동영상 제어 디스플레이 크기 CPU 사용량(%) GPU 사용량(%)
h264 320p 30fps 320p 1.82 4.56
h264 1080p 30fps 360p 13.4 4.84
h264 1080p 30fps 1080p 13.04 15.14

텍스처 렌더링

동영상 제어 디스플레이 크기 CPU 사용량(%) GPU 사용량(%)
h264 320p 30fps 320p 1.28 5.06
h264 1080p 30fps 360p 4.26 12.66
h264 1080p 30fps 1080p 4.78 14.72

작은 해상도를 렌더링할 때도 이 글에서 제시한 렌더링 방식의 성능은 여전히 ​​만족스러운 수준임을 알 수 있는데, 해상도가 상대적으로 높으면 CPU 사용률이 많이 올라가고 GPU 사용률은 디스플레이 크기에 영향을 받는다. 컨트롤의. 텍스처 방법은 성능이 더 좋고 변동이 적습니다.


4. 완전한 코드

https://download.csdn.net/download/u013113678/87121930
참고: 이 기사의 구현 성능은 특별히 좋지 않습니다. 필요에 따라 다운로드하십시오.
전체 코드, 버전 3.0.4 및 3.3.8을 포함하는 Flutter 프로젝트가 성공적으로 실행되고 있으며 현재 ios, macos 구현을 포함하지 않습니다. 카탈로그 설명은 다음과 같습니다.
여기에 이미지 설명 삽입


요약하다

위 내용은 오늘 제가 이야기 하고자 하는 내용입니다 FFI+CustomPainter를 사용하여 동영상 렌더링을 구현하는 것은 저자가 탐구한 방법이며 원리는 복잡하지 않고 성능은 거의 사용할 수 없다고 할 수 있으며 작은 이미지 렌더링에 적합합니다 . . 글로 써서 내보내는 것도 노드 역할을 하고 이를 바탕으로 계속해서 최적화를 하는 것입니다. 전반적으로 이것은 좋은 예이며 탐색할 가치가 있는 솔루션입니다.

추천

출처blog.csdn.net/u013113678/article/details/127990764