目的
正在写一个视频与AI的工具,从接入,算法处理,转发,存储, 到调用AI进程,并且与AI进程进行交互,插件化,脚本化,做得比较辛苦,期间的进程和线程交互以及结果渲染到UI上逻辑必须严谨,否则以c++ 来说,UI处理不好,主进程容易崩溃。
下图是为了进行交互的工具,与接入的各类方式,如rtsp 接入,rtp直接接入,ps GB28181 接入,rtmp接入,ts流接入等等,工具的制作异常艰辛,抛弃了很多项目,心无旁骛,淡看过往,—看似洒脱,实属无奈。
2 深思
2.1 保证核心通用
既然是一个比较先进的工具,创新是必然的,然而我将创新放到了插件中,创新必须包含很多的灵活性,因为你是一种工具,是一种“空”,而世上只有空才能包容万物。组件插件化,脚本化,是保证核心逻辑通用的方式。
2.2 插件化
将图像运行,转发,AI,进行插件化,为每一路视频图像进行插件管理。接入是一种统一的外接入,为了使得动态库和主进程进行
3 数据结构
定义接入的数据结构要考虑很多问题
typedef struct s_param
{
const char* name = NULL;//所属名称 如rtspclient
const char* url = NULL;//唯一名称,or ip+port
uint8_t* data = NULL;
int len = 0; //len = -1 收到结束
uint32_t ssrc;
void* obj = NULL; //create 中传入的obj
const char* ipfrom = NULL;
int width = 0;
int height = 0;
uint16_t fport;
uint16_t seq;//兼容rtp
int codeid; //与ffmpeg兼容
int payload = -1; //payload type
int64_t pts;
void* decoder = NULL;
uint8_t* extradata = NULL;
int extradatalen = 0;
//bool isnew = true; //是否是一个新的传输
//void* rtspworker = NULL;
}s_param;
//回调函数是压缩流
typedef void(*func_callback)(s_param* param);
typedef void(*func_callback_new)(void* obj, const char* url);
//len >0 vps sps pps 信息
//len = 0
//len = -1 无法连接
//len = -2 read 出错
//len = -3 其他错误
typedef void(*func_callback_extra)(uint8_t* data, int len);
//
制作过程中接入视频以后才知道很多东西不能光靠想象,即使有几十年的视频经验,程序依然需要打磨,平时太多的demo制作,没有产品化好的弊端完全显露出来,即使是一个数据结构,也是改之又改。为了在工具中能够进行视频拼接,加入了很多方法:如下图所示
视频拼接是一个比较大的话题,我们以后再进行描述。以下是视频拼接的第一步算法,鱼眼矫正处理,加载了鱼眼矫正的插件动态库。
鱼眼矫正算法加载
extern "C"
{
_declspec(dllexport) const char* WINAPI func_name();
_declspec(dllexport) int WINAPI _fastcall func_worker(uint8_t* data, int w, int h, uint8_t* outdata);
_declspec(dllexport) int WINAPI func_no();
}
以上是插件的接口定义,为了达到视频的无极缓存,需要从申请内存开始,到动态库图像算法,到转发,存储,再接入AI脚本,与AI脚本共享内存,一直使用同一份内存。
测试-图像鱼眼矫正算法载入
//画一帧图像
void func_draw(int px, int py, int drawrect)
{
AVFrame* frame = NULL;
if (v_frames.dequeue(&frame))
{
/*cv::Mat nmat;
nmat.cols = frame->width;
nmat.rows = frame->height;
nmat.data = frame->data[0];*/
//装载畸形矫正算法
//if (!v_funcs.empty())
auto iter = v_plugins.begin();
while (iter != v_plugins.end())
{
(*iter)->FUNC_worker(frame->data[0], frame->width, frame->height,NULL);
iter++;
}
v_drawer.func_draw(px, py, frame, drawrect);
{
if (v_script != nullptr && !v_script->IsStop())
{
if (frame->key_frame)
{
//写入共享内存
int w = frame->width;
int h = frame->height;
mem_info_ptr ptr = v_script->mem_getch_process(frame->data[0], w, h);
if (ptr != nullptr)
{
v_script->v_in.Push(ptr);
v_script->Notify();//通知取队列
}
}
if (!v_script->v_out.IsEmpty())
{
//开始画子画面//子画面中开始画结果
//v_script->
}
}
std::lock_guard<std::mutex> lock(v_mutex_cache);
if (v_cache != NULL)
{
av_freep(&v_cache->data[0]);
av_frame_free(&v_cache);
}
//保存最后一帧当前帧
v_cache = frame;
//是否需要和python脚本进程交互
}
}
}
鱼眼矫正图像是为了做多个摄像头做拼接而做的算法,整个逻辑接入,到绘制到UI上,都通过一个回调而进入核心绘制程序,所以回调的地方会有很多逻辑分离代码。
4 进程通信
1 lua
与脚本交互,1 是lua语言, 2 是javascript语言,3 是glsl 语言 4 就是需要常用的AI 处理 python
lua的想法很一般,就是c++ 提供API, 提供httpserver和websocket server,lua组织一下基本启动,当然,lua有一个好处就是不用配置文件了,配置文件直接就包含在lua 脚本里面就行
2 javascript
更简单,直接调用javascript 函数,反之javascript调用c++ 函数,嵌入v8 引擎。
2 python
这个是重点,就是c++ 到底如何驱动python, 需要一个进程管理还是简单调用,后来做起来发觉,确实需要进程管理,c++使用boost库做进程管理是比较方便的,不过先使用更简单的方案,就是使用异步调用进程和内存共享,
#include <iostream>
#include <string>
#include <vector>
//#include <boost/filesystem.hpp>
#include <algorithm>
#include <boost/process.hpp>
int main_face()
{
//std::system("python.exe face.py");
namespace bp = boost::process;
//std::string child_process_name = "child_process";
//std::cout << "main_process: before spawning" << std::endl;
bp::child c(bp::search_path("python.exe"), "face.py");
tcpclient client;
while (c.running())
{
//printf("running");
client.send();
client.read();
}
c.wait(); //wait for the process to exit
int result = c.exit_code();
}
使用boost去启动进程,做进程记录,并且在UI中要有处理进程管理的管理器。
使用tcp 协议去发送视频图像的某一帧已经准备好了在内存共享中,接收到tcp消息表明在内存共享中已经处理完毕,把消息插入到队列中,主进程进行发现并且渲染。
其他方式:
使用std::system 去启动进程,用c++ 的高级特性,async进行循环等待,依然使用tcp 或者udp进行通信,注意
void f()
{
std::system("python.exe face.py");
}
int main()
{
auto fut = std::async(f); // 异步执行f
udpclient client;
while (fut.wait_for(1000ms) != // 循环直到f执行结束
std::future_status::ready) //意味着python程序已经退出进程
{
client.send(w,h,number)
client.read(w,h,number)
//tcp 或者udp 进行通信
//消息入队列
queue.push()
}
}
使用boost 来进行进程上的管理,或者自行异步进行进程管理都是可行的,udp方式比tcp方式优势是udp其实本身也是半“异步的”, 在不绑定udp 的情况下,不用考虑udp 缓存处理,在绑定udp的情况下,udp 缓存到极限则会阻塞,我们只要加入udp 在接收到数据的情况下知道python进程已经准备好了就行,tcp 处理也是一样。
这是系列的一,我会继续完善这个工具,直到他比较方便、代码清晰的时候进行开源。下一篇文章,必然是这个工具取得比较大的进步了