• flutter 使用texture实现Linux渲染视频



    前言

    flutter渲染视频的方法有多种,比如texture、platformview、ffi,其中texture是通过flutter自己提供的一个texture对象与dart界面关联后进行渲染,很容易搜索到android和ios的相关资料,但是Linux上却几乎找不到任何资料。通过查看一些开源库的代码,才找到Linux上使用flutter texture的方法,在这里做一个简单的介绍。


    一、如何实现?

    1、定义Texture控件

    在界面中定义一个Texture

    Container(
      width: 640,
      height: 360,
      child: Texture(
      textureId: textureId,
    ))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2、创建Texture对象

    (1)继承FlPixelBufferTexture

    此处代码为dart_vlc源码,因为是一个独立对象所有可以直接拿来用,自己继承实现也基本差不多,所以就没必要造轮子了。

    #ifndef VIDEO_OUTLET_H_
    #define VIDEO_OUTLET_H_
    #include <flutter_linux/flutter_linux.h>
    #include <gtk/gtk.h>
    struct _VideoOutletClass {
      FlPixelBufferTextureClass parent_class;
    };
    struct VideoOutletPrivate {
      int64_t texture_id = 0;
      uint8_t* buffer = nullptr;
      int32_t video_width = 0;
      int32_t video_height = 0;
    };
    G_DECLARE_DERIVABLE_TYPE(VideoOutlet, video_outlet, DART_VLC, VIDEO_OUTLET,
                             FlPixelBufferTexture)
    G_DEFINE_TYPE_WITH_CODE(VideoOutlet, video_outlet,
                            fl_pixel_buffer_texture_get_type(),
                            G_ADD_PRIVATE(VideoOutlet))
    static gboolean video_outlet_copy_pixels(FlPixelBufferTexture* texture,
                                             const uint8_t** out_buffer,
                                             uint32_t* width, uint32_t* height,
                                             GError** error) {
      auto video_outlet_private =
          (VideoOutletPrivate*)video_outlet_get_instance_private(
              DART_VLC_VIDEO_OUTLET(texture));
      *out_buffer = video_outlet_private->buffer;
      *width = video_outlet_private->video_width;
      *height = video_outlet_private->video_height;
      return TRUE;
    }
    static VideoOutlet* video_outlet_new() {
      return DART_VLC_VIDEO_OUTLET(g_object_new(video_outlet_get_type(), nullptr));
    }
    static void video_outlet_class_init(VideoOutletClass* klass) {
      FL_PIXEL_BUFFER_TEXTURE_CLASS(klass)->copy_pixels = video_outlet_copy_pixels;
    }
    static void video_outlet_init(VideoOutlet* self) {}
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    (2)注册Texture

    static FlTextureRegistrar* _registrar;
    //创建自定义的texture对象
     auto  texture=video_outlet_new();
     //注册对象
     fl_texture_registrar_register_texture(_registrar,FL_TEXTURE(texture));
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3、关联TextureId

    dart

      int textureId = -1;
      if (textureId < 0) {
      //调用本地方法获取textureId 
      methodChannel.invokeMethod('startPreview',<String,dynamic>{'path':'test.mov'}).then((value) {
      textureId = value;
      setState(() {
      print('textureId ==== $textureId');
      });
      });
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    c++

     //methodchannel的startPreview方法实现,此处略
     //texure指针的地址即为textureId
      g_autoptr(FlValue) result = fl_value_new_int((int64_t)texture);
      //设置返回值
      response = FL_METHOD_RESPONSE(fl_method_success_response_new(result));
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4、写入rgba

    static FlTextureRegistrar* _registrar;
    //取得自定义的内部对象
    auto video_outlet_private =(VideoOutletPrivate*)video_outlet_get_instance_private(texture);
    //设置视频帧宽高
    video_outlet_private->video_width = width;
    video_outlet_private->video_height = height;
    //设置rgba数据
    video_outlet_private->buffer = data[0];
    //通知渲染
    fl_texture_registrar_mark_texture_frame_available(_registrar, FL_TEXTURE(texture));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    注:FlTextureRegistrar的获取方法为:

    //定义TextureRegistrar对象
    static FlTextureRegistrar* _registrar;
    //插件注册代码,这里的插件名为ffplay_plugin,此方法为官方生成代码
    void ffplay_plugin_register_with_registrar(FlPluginRegistrar* registrar) {
      FfplayPlugin* plugin = FFPLAY_PLUGIN(
          g_object_new(ffplay_plugin_get_type(), nullptr));
      g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
      g_autoptr(FlMethodChannel) channel =
          fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar),
                                "ffplay_plugin",
                                FL_METHOD_CODEC(codec));   
        //获取TextureRegistrar对象                                             
      _registrar=fl_plugin_registrar_get_texture_registrar(registrar);                                                                     
      fl_method_channel_set_method_call_handler(channel, method_call_cb,
                                                g_object_ref(plugin),
                                                g_object_unref);
      g_object_unref(plugin);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    二、示例

    1.使用ffmpeg解码播放

    main.dart

    import 'package:flutter/material.dart';
    import 'package:flutter/services.dart';
    MethodChannel methodChannel = MethodChannel('ffplay_plugin');
    void main() {
      runApp(MyApp());
    }
    class MyApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key? key, required this.title}) : super(key: key);
    
      final String title;
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      int _counter = 0;
      int textureId = -1;
      Future<void> _createTexture() async {
      print('textureId = $textureId');
      //调用本地方法播放视频
      if (textureId < 0) {
      methodChannel.invokeMethod('startPreview',<String,dynamic>{'path':'https://sf1-hscdn-tos.pstatp.com/obj/media-fe/xgplayer_doc_video/flv/xgplayer-demo-360p.flv'}).then((value) {
      textureId = value;
      setState(() {
      print('textureId ==== $textureId');
      });
      });
      }
      }
      @override
      Widget build(BuildContext context) {
      return Scaffold(
      appBar: AppBar(
      title: Text(widget.title),
      ),
      //控件布局
      body: Center(
      child: Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
      if (textureId > -1)
      ClipRect (
      child: Container(
      width: 640,
      height: 360,
      child: Texture(
      textureId: textureId,
      )),
      ),
      ],
      ),
      ),
      floatingActionButton: FloatingActionButton(
      onPressed: _createTexture,
      tooltip: 'createTexture',
      child: Icon(Icons.add),
      ),
      );
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    定义一个插件我这里是fflay_plugin。
    fflay_plugin.cc
    相关对象的定义

    static FlTextureRegistrar* _registrar;
    class PlayData {
    public:
        //Play中封装了ffmpeg
    	Play* play;
    	VideoOutlet* flutter_pixel_buffer;
    	int64_t texture_id;
    };
    static std::map<int64_t, PlayData*> playMap;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    获取FlTextureRegistrar对象

    //插件注册代码,这里的插件名为ffplay_plugin,此方法为官方生成代码
    void ffplay_plugin_register_with_registrar(FlPluginRegistrar* registrar) {
      FfplayPlugin* plugin = FFPLAY_PLUGIN(
          g_object_new(ffplay_plugin_get_type(), nullptr));
      g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
      g_autoptr(FlMethodChannel) channel =
          fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar),
                                "ffplay_plugin",
                                FL_METHOD_CODEC(codec));   
        //获取TextureRegistrar对象                                             
      _registrar=fl_plugin_registrar_get_texture_registrar(registrar);                                                                     
      fl_method_channel_set_method_call_handler(channel, method_call_cb,
                                                g_object_ref(plugin),
                                                g_object_unref);
      g_object_unref(plugin);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    methodChannel部分

     if (strcmp(method, "startPreview") == 0){
           //获取参数
           auto arguments = fl_method_call_get_args(method_call);
           auto path =  fl_value_get_string(fl_value_lookup_string(arguments, "path"));  
           //创建texture
           auto  texture=video_outlet_new();
           //注册texture
           fl_texture_registrar_register_texture(_registrar,FL_TEXTURE(texture));
           PlayData* pd = new PlayData;	
           //初始化播放器		
           pd->play = new Play;
           pd->flutter_pixel_buffer=texture;
           pd->texture_id = (int64_t)texture;
           playMap[pd->texture_id] = pd;
           //播放视频回调
           pd->play->Display = [=](unsigned char* data[8], int linesize[8], int width, int height, AVPixelFormat format) {
           //设置视频数据
           auto video_outlet_private =(VideoOutletPrivate*)video_outlet_get_instance_private(pd->flutter_pixel_buffer);
    				video_outlet_private->video_width = width;
    				video_outlet_private->video_height = height;
    				video_outlet_private->buffer = data[0];
    	    //通知渲染
    			 fl_texture_registrar_mark_texture_frame_available(
                         _registrar, FL_TEXTURE(pd->flutter_pixel_buffer));
    			};
    	//开始播放视频
       pd->play->Start(path, AV_PIX_FMT_RGBA);
       //返回textureId
       g_autoptr(FlValue) result = fl_value_new_int((int64_t)texture);
       response = FL_METHOD_RESPONSE(fl_method_success_response_new(result));
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    效果预览

    在这里插入图片描述


    总结

    以上就是今天要讲的内容,flutter在linux上渲染视频,还是有点不容易的,一是缺乏相关资料,二是flutter在Linux上的本地代码是另外一套封装与windows完全不相同。而且采用的是c语言自定义一套面向对象规则的方式,当然编译器是clang++我们可以使用c++的方式编码。总的来说,flutter是可以在Linux实现视频渲染的,如果要进一步优化则需要用gltexture或者本地窗口渲染。

  • 相关阅读:
    寒冬下的跑路与裁员...
    Spring基础与核心概念
    5.前后端不分离项目的部署
    什么是final修饰 使用final修饰类、方法、变量的区别?
    【EI会议征稿通知】第十届机械工程、材料和自动化技术国际会议(MMEAT 2024)
    MySQL 三大日志(bin log、redo log、undo log)
    波特率,奇偶校验
    Netty源码学习8——从ThreadLocal到FastThreadLocal(如何让FastThreadLocal内存泄漏doge)
    若依微服务特殊字符串被过滤的解决办法
    Unity开发之观察者模式(事件中心)
  • 原文地址:https://blog.csdn.net/u013113678/article/details/125509450