天天看点

Flutter 使用Texture实现Linux渲染视频Flutter视频渲染系列前言一、如何实现?二、示例三、完整代码总结

Flutter视频渲染系列

第一章 Android使用Texture渲染视频

第二章 Windows使用Texture渲染视频

第三章 Linux使用Texture渲染视频(本章)

第四章 全平台FFI+CustomPainter渲染视频

文章目录

  • Flutter视频渲染系列
  • 前言
  • 一、如何实现?
    • 1、定义Texture控件
    • 2、创建Texture对象
      • (1)继承FlPixelBufferTexture
      • (2)注册Texture
    • 3、关联TextureId
    • 4、写入rgba
  • 二、示例
    • 1.使用ffmpeg解码播放
  • 三、完整代码
  • 总结

前言

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,
))
           

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
           

(2)注册Texture

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

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');
  });
  });
  }
           

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));
           

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));
           

注: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.使用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),
  ),
  );
  }
}
           

定义一个插件我这里是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;
           

获取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);
}
           

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

效果预览

Flutter 使用Texture实现Linux渲染视频Flutter视频渲染系列前言一、如何实现?二、示例三、完整代码总结

三、完整代码

https://download.csdn.net/download/u013113678/87096317

包含完整代码的flutter项目,版本3.0.4、3.3.8都成功运行,需要自行安装ffmpeg库版本最好为4.3或5.0.1。目录说明如下。

注:由于笔者在Ubuntu上采用静态加载ffmpeg so库出现了glibc冲突问题没有解决,所以采用了动态加载so的方式,DllImportUtils的作用只是动态加载so,具体可查看《C++ 使用宏加载动态库》。

Flutter 使用Texture实现Linux渲染视频Flutter视频渲染系列前言一、如何实现?二、示例三、完整代码总结

总结

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

继续阅读