第一章 Android使用Texture渲染视频
第二章 Windows使用Texture渲染视频
第三章 Linux使用Texture渲染视频(本章)
第四章 全平台FFI+CustomPainter渲染视频
第五章 Windows使用Native窗口渲染视频
第六章 桌面端使用texture_rgba_renderer渲染视频
flutter渲染视频的方法有多种,比如texture、platformview、ffi,其中texture是通过flutter自己提供的一个texture对象与dart界面关联后进行渲染,很容易搜索到android和ios的相关资料,但是Linux上却几乎找不到任何资料。通过查看一些开源库的代码,才找到Linux上使用flutter texture的方法,在这里做一个简单的介绍。
在界面中定义一个Texture
Container(
width: 640,
height: 360,
child: Texture(
textureId: textureId,
))
此处代码为dart_vlc源码,因为是一个独立对象所有可以直接拿来用,自己继承实现也基本差不多,所以就没必要造轮子了。
#ifndef VIDEO_OUTLET_H_
#define VIDEO_OUTLET_H_
#include
#include
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
static FlTextureRegistrar* _registrar;
//创建自定义的texture对象
auto texture=video_outlet_new();
//注册对象
fl_texture_registrar_register_texture(_registrar,FL_TEXTURE(texture));
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));
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);
}
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));
}
效果预览
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在linux上渲染视频,还是有点不容易的,一是缺乏相关资料,二是flutter在Linux上的本地代码是另外一套封装与windows完全不相同。而且采用的是c语言自定义一套面向对象规则的方式,当然编译器是clang++我们可以使用c++的方式编码。总的来说,flutter是可以在Linux实现视频渲染的,如果要进一步优化则需要用gltexture或者本地窗口渲染。