MediaServer

  EmxMediaServer包含如下功能模块,程序启动时会逐一的去启动各个模块,其中每个模块启动的时候会将自身配置的支持的所有通道按顺序启动。销毁顺序是创建的逆过程

System(系统)->Vi(视频输入)->Isp(画质处理)->Venc(视频编码)->
Aenc(音频编码)->Adec(音频解码)->Snap(缩略图抓拍)->Osd(水印)->
QrScan(二维码扫描)->Ai(智能模块)->Vo(视频输出[可选])

  芯片对接需要实现的代码放在chip/cvitek/cv182xDefault下,以Default+模块名.xpp命名。所有模块都有一些相同的特性,了解这些特性对于后续对接开发有很大帮助,这里为了方便描述,我们假设一个模块名为Xxxx(例如Venc,Aenc...),Xxxx模块对应的源文件是DefaultXxxx.cppDefaultXxxx.hpp,在DefaultXxxx.hpp定义了一个同名的类DefaultXxxx这个类继承了MediaServerXxxxChn,它是Emx实现的一个基础类,表示一个模块的某个通道,所以实际上我们是在实现模块通道的回调,我们不需要过于关心MediaServerXxxxChn,因为在DefaultXxxx类中已经将需要实现的回调函数事先写了出来,并在DefaultXxxx.cpp中进行了定义,我们只需要重点关注这些定义函数的实现即可。   通常DefaultXxxx会有3个通用的回调需要实现,OnCreate表示模块某个通道的创建回调,OnDestroy表示某个通道的销毁回调,OnParamChanged表示模块某个通道的参数发生改变的回调。DefaultXxxx的通道号是从父类继承来的,成员变量m_chn,关于通道号的信息,可以回看3.1.概念解释中的通道号部分。这里需要明确注意的是,DefaultXxxx类实现的是模块通道,而非模块。另外强调一点就是模块通道的OnDestroy回调必须保证销毁的彻底,保证是OnCreate的完整逆过程,不能出现内存泄漏或者第二次OnCreate失效问题

ErrCodeE OnCreate() override;
void OnDestroy() override;
ErrCodeE OnParamChanged() override;
ErrCodeE
错误码定义
Definition: EmxTypeDef.hpp:29

  OnParamChanged回调表示参数的改变,新旧参数分别存储在成员变量m_paramNewm_param中,当模块通道OnCreate的阶段,此时并没有新参数m_paramNew,通道的配置使用的是m_param,当模块通道OnDestroy的阶段同样如此,只有OnParamChanged的时候会涉及到新旧参数,为了方便开发,一般会在DefaultXxxx中定义一个参数指针m_pParam,切换m_pParam的指向即可使用不同的参数,一般来将我们可以简单粗暴的将OnParamChanged实现为使用m_pParam=m_param对模块通道进行Destroy,然后再使用m_pParam=m_paramNew对模块通道进行Create,从而实现参数配置的切换。然而当一个模块通道的Destroy-Create操作比较耗时,就需要对参数的改变有针对性的处理,芯片厂商需要对m_paramNewm_param进行比对来判断是需要重启,还是执行一个简单的设置操作,例如如果Venc的编码格式发生改变可能需要重启通道,但是编码的码率改变只需要执行类似CHIP_SetVencBitRate(bitrate)即可,这样做的原因是整个EmxMediaServer是一个准单线程的服务程序,几乎所有的函数运行在同一个线程中,某个操作的阻塞会导致其他所有函数无法得到执行,例如视频编码每一帧的输出都是在此线程中完成,如果线程中某个回调有过于耗时的操作则会导致视频的丢帧

  在每个模块的详细说明中,可以被阻塞的回调都会特别说明指出,没有指明的回调均是不可被阻塞的

  一般来讲在OnCreate阶段必定会涉及到配置文件的加载,加载配置文件使用如下方式:

Json::Value root;
MediaServerCtx::GetInst()->GetCfg(m_module->m_name.c_str(), root);
auto &json = root[m_chn]["attr"];

json中存储的就是这个模块通道的配置文件,这个配置文件由芯片厂商自行编写,m_module->m_name.c_str()是模块名称字符串xxxx   除了上面提到的3个通用的回调之外,一些模块还有一些自身特有的回调需要实现,例如Venc模块需要实现FlushIDR回调来实现I帧的强制刷新,这些特色回调的实现方式会在下面各个模块的详细开发说明中指出。下面各个模块的说明顺序也是芯片对接推荐的实现顺序,建议按照此顺序实现各个模块并完成测试。当然除了System模块需要首先实现外,其他所有的模块都没有强依赖关系,可以并行实现。代码中有详细的注释甚至还有伪代码供参考,看我多贴心

1. System

  System模块实现的功能是整个系统的初始化,例如媒体内存的分配,sensor配置的加载,PipeLine的构建等,不同厂商在此模块中实现的内容可能不尽相同,但最终的目的是当此模块启动完成后,后续其他模块可正常启动。一般来讲System模块只有一个通道0,这个无需做什么特殊处理,还是按照模块多通道的模板来实现即可。System模块的接口比较简单只需要实现OnCreateOnDestroy两个回调,没有OnParamChanged回调,但是System的配置文件应该是最复杂的,也是芯片厂商需要着重实现的部分,需要保证配置文件的灵活性,减少版本的发布

需要实现的接口:
virtual ErrCodeE OnCreate() = 0;
virtual void OnDestroy() = 0;

2. Venc

  Venc模块负责实现一路视频编码和参数配置,芯片厂需要完成编码通道的创建,编码数据的获取,参数的配置生效,以及I帧的刷新

需要实现的接口:
virtual ErrCodeE OnCreate() = 0;
virtual void OnDestroy() = 0;
virtual ErrCodeE OnParamChanged() = 0;
virtual ErrCodeE FlushIDR() = 0;
virtual ErrCodeE GetOneFrame(FrameData &frame) = 0;
virtual void ReleaseOneFrame() = 0;

>OnCreate

  这个函数已经在函数模板中部分实现,芯片厂商需要主要实现配置文件的解析以及通道的创建函数CreateVenc,另外,CreateVenc创建完成后调用了父类提供的StartStream函数,这个函数调用完后就会陆续收到GetOneFrame获取一帧编码视频的请求。还有一个固有成员变量m_paramChange也会在这里进行赋值,这个变量用来指示编码参数发生了改变,会随着每一帧的数据发送出去,方便其他接收端模块获取到此信息后做出相应调整

>OnParamChanged

  这个函数已经在函数模板中部分实现,采用的方式是使用旧的参数停止编码,然后使用新的参数启动编码的粗暴方式,这里可以先暂时保留这种实现方式,不过当发现这种方式耗时比较久的时候需要更改为判断m_param和m_paramNew的详细改变,仅当必要时才进行重启操作。另外m_paramChange的赋值也是如此,当仅仅是修改了bitRate/iFrameInv/fps/rcMode/drawRect等对后续编码接收没有太大影响的时候不需要对其进行赋值,防止接收端频繁应对

>GetOneFrame(可被阻塞)

  当StartStream被调用之后,GetOneFrame函数会被循环触发,不同于其他的回调,这个回调是在EmxMediaServer主线程之外运行的,所以一方面这个函数中允许一定程度的阻塞操作,例如类似ChipVencGetStream(m_vencChn, &m_stream, 1000)这样的操作,另一方面需要注意不同线程间访问共享变量或内存时的安全性。这个回调的目的是填充FrameData结构体,具体操作可以参考此回调内部伪代码,需要注意的是在ReleaseOneFrame回调被执行前frame.data所指向内存需要保证一直有效,不能被释放,一般来讲需要芯片厂商使用成员变量来存储这些数据,例如伪代码中的m_stream成员变量

>ReleaseOneFrame

  ReleaseOneFrame回调用于供芯片厂商释放GetOneFrame中申请的资源,例如m_stream。**注意**仅当GetOneFrame返回ErrCodeE::SuccessReleaseOneFrame才会被执行,所以需要GetOneFrame内部失败的时候注意资源的释放。假设每次都是成功的,那么GetOneFrameReleaseOneFrame会被顺序的同步依次循环执行

3. Aenc

  Aenc模块负责实现一路音频编码和参数配置,芯片厂需要完成音频采集通道的创建,原始PCM数据的获取以及各项参数的配置生效

需要实现的接口:
virtual ErrCodeE OnCreate() = 0;
virtual void OnDestroy() = 0;
virtual ErrCodeE OnParamChanged() = 0;
virtual ErrCodeE SetVolume(int32_t volume) = 0;
virtual ErrCodeE Mute(bool ena) = 0;
virtual ErrCodeE GetOneFrame(FrameData &frame) = 0;
virtual void ReleaseOneFrame() = 0;

>OnCreate & OnParamChanged & GetOneFrame & ReleaseOneFrame

  这些回调的说明与Venc模块基本一致,参考Venc即可,注意GetOneFrame需要的是PCM原始数据,不需要进行编码

>SetVolume & Mute

  这两个回调负责实现控制音频输入的音量。这里有两点需要特别说明   关于音量的大小,外部传入的volume是0-100的数值,一般芯片有自己的音量增益范围,需要厂家将其线性映射到自身的增益   关于音量和静音的逻辑,当SetVolume被调用并且音量被更新时如果当前是静音状态,那么需要取消掉静音状态,并设置新的音量。当Mute被调用并且静音被取消时需要恢复最后一次SetVolume设置的音量

4. Snap

  Snap模块负责实现一路缩略图编码

需要实现的接口:
virtual ErrCodeE OnCreate() = 0;
virtual void OnDestroy() = 0;
virtual ErrCodeE GetOneSnap(std::vector<EmxData> &data) = 0;
virtual void ReleaseOneSnap() = 0;

>OnCreate

  这个函数已经在函数模板中部分实现,芯片厂商需要主要实现配置文件的解析,另外目前Snap通道没有参数配置回调

>GetOneSnap(可被阻塞)

  GetOneSnap与Venc和Aenc中的GetOneFrame不同,OnCreate之后GetOneSnap不会被循环触发,而是通过用户的trigger操作单词触发,厂商需要在此函数内部获取到一张编码后的缩略图,并放入data中,同样data需要在ReleaseOneSnap被执行前保持有效。

>ReleaseOneSnap

  用于释放GetOneSnap中获取到的资源,**注意**仅当GetOneSnap返回ErrCodeE::SuccessReleaseOneSnap才会被执行,所以需要GetOneSnap内部失败的时候注意资源的释放

5. Adec

  Adec模块负责实现一路音频解码和参数配置,芯片厂需要完成音频播放通道的创建,原始PCM数据的播放以及各项参数的配置生效

需要实现的接口:
virtual ErrCodeE OnCreate() = 0;
virtual void OnDestroy() = 0;
virtual ErrCodeE OnParamChanged() = 0;
virtual ErrCodeE SetVolume(int32_t volume) = 0;
virtual void SendToAo(const int16_t *pcm, int32_t pktNum) = 0;
virtual PlayStatusE GetPlayStatus() = 0;

>OnCreate & OnParamChanged

  这些回调的说明与Venc模块基本一致,参考Venc即可

>SetVolume

  回调的说明与Aenc模块基本一致,参考Aenc即可,Adec没有静音操作

>SendToAo(可被阻塞)

  这里需要实现播放PCM数据,注意参数pktNum是pcm采样点数,不是字节数,如果芯片厂家的类似ChipAoSendFrame这样的播放PCM的函数需要的是字节数,则应当pktNum x 2处理

>GetPlayStatus

  很多情况下应用层需要知道当前是否有声音正在播放,这个回调负责获得当前的播放情况,并且大多数设备为了消除掉安静环境下由硬件电路干扰带来的喇叭底噪,会在没有声音播放的时候将喇叭功放的开关关闭掉,这个操作已经由EmxMediaServer实现,但同样需要通过这个接口获取声音是否播放的状态

6. Vi

  Vi模块目前仅负责实现Sensor画面的镜像和反转,Vi模块目前比较简单,需要注意一点就是mirror和flip的初始状态。拿flip举例(mirror同理),假设芯片提供sdk接口函数SetFlip来配置vi的翻转,SetFlip(0)表示不翻转,SetFlip(1)表示翻转。由于sensor的电路或者结构设计导致出厂sensor是反装的状态,那么要保证相机出厂画面正常就需要初始的状态就将画面进行翻转。此时当用户配置翻转的时候SetFlip反而要配置成0,当用户配置不翻转的时候才SetFlip(1),因此就有了下表。综合一下,实际SetFlip的值应当是初始值与参数值的异或。芯片厂需要保留DefaultVi类中的m_originFlipm_originMirror来加载并保存初始状态,以便在SetFlip的时候使用

用户flip:0 用户fpli:1
初始flip:0 SetFlip(0) SetFlip(1)
初始flip:1 SetFlip(1) SetFlip(0)

需要实现的接口:
virtual ErrCodeE OnCreate() = 0;
virtual void OnDestroy() = 0;
virtual ErrCodeE OnParamChanged() = 0;

7. QrScan

  QrScan实现二维码的扫描,针对芯片对接来讲,芯片厂商需要实现的是获取一张YUV数据,并将Y通道数据送出

需要实现的接口:
virtual ErrCodeE OnCreate() = 0;
virtual void OnDestroy() = 0;
virtual Size GetChnImgSize() = 0;
virtual ErrCodeE GetOneImg(uint8_t **dataY) = 0;
virtual void ReleaseOneImg(uint8_t *dataY) = 0;

>OnCreate

  除了必要的参数加载和初始化之外,需要在OnCreate阶段获取到当前通道画面的宽高信息并记录下来,之后会收到GetChnImgSize回调来获取宽高信息

>GetOneImg(可被阻塞)

  当OnCreate被调用之后,GetOneImg函数会被循环触发,不同于其他的回调,这个回调是在EmxMediaServer主线程之外运行的,所以一方面这个函数中允许一定程度的阻塞操作,另一方面需要注意不同线程间访问共享变量或内存时的安全性。这个回调的目的是赋值dataYYUV的Y通道数据,具体操作可以参考此回调内部伪代码,需要注意的是在ReleaseOneImg回调被执行前dataY所指向内存需要保证一直有效,不能被释。

>ReleaseOneImg

  ReleaseOneImg回调用于供芯片厂商释放GetOneImg中申请的资源。**注意**仅当GetOneImg返回ErrCodeE::SuccessReleaseOneImg才会被执行,所以需要GetOneImg内部失败的时候注意资源的释放。假设每次都是成功的,那么GetOneImgReleaseOneImg会被顺序的同步依次循环执行

8. Isp

  Isp模块实现对图像画质的调节,夜市模式的配置以及ISP信息的收集

需要实现的接口:
virtual ErrCodeE OnCreate() = 0;
virtual void OnDestroy() = 0;
virtual ErrCodeE OnParamChanged() = 0;
virtual ErrCodeE SetRunMode(RunModeE mode) = 0;
virtual ErrCodeE GetRunMode(RunModeE &mode) = 0;
virtual ErrCodeE Collection(Info &info) = 0;

>OnCreate

  除了必要的参数加载和初始化之外,需要在OnCreate阶段初始化夜视状态为白天。

>OnParamChanged

{
"expMode": [0, 1],
"autoMaxExpGain": {"min": 0, "max": 6},
"compensation": {"min": 0, "max": 255},
"manualExpGain": {"min": 0, "max": 6},
"expTime": {"min": 0, "max": 10000},
"brightness": {"min": 0, "max": 255},
"sharpness": {"min": 0, "max": 255},
"hue": {"min": 0, "max": 255},
"contrast": {"min": 0, "max": 255},
"saturation": {"min": 0, "max": 255},
"deNoise": {"min": 0, "max": 255},
"wbMode": [0, 1],
"redGain": {"min": 0, "max": 255},
"blueGain": {"min": 0, "max": 255},
"antiFlickerHz": [50, 60],
"forceAntiFlickerEna": [0, 1]
}

  各个参数的取值范围如上

>SetRunMode

  这里需要根据自身配置和外部参数切换为特定的运行模式

>Collection

struct Info {
int chn;
int iso;
int redGain;
int blueGain;
int luma;
};

  Collection回调在启动后会定时的触发,用来收集ISP信息,目前主要目的是为了其他模块利用这些消息来实现软光敏。不同的芯片厂商的ISP信息可能并不相同,首先Emx并不严格要求取值范围,但是需要保证不同的光照情况下这些值的变化是显著的稳定的,并且变化的方向是符合要求的,可以被用来实现软光敏。

9. Osd

  Osd模块实现水印的叠加,目前支持的有三种类型,分别是时间戳,文字和图片

需要实现的接口:
virtual ErrCodeE OnCreate() = 0;
virtual void OnDestroy() = 0;
virtual Size GetChnPicSize() = 0;
virtual int RegionCreateAll(std::list<RegionInfo> &region) = 0;
virtual void RegionDestroyAll(const std::list<RegionInfo> &region) = 0;
virtual void SetBitMap(int region, uint8_t *pix, int w, int h) = 0;
virtual ErrCodeE ImageToCanvas(int region, const std::string &data) = 0;

>OnCreate

  除了必要的参数加载和初始化之外,需要在OnCreate阶段记录一下当前osd通道所叠加背景图像的宽高信息,之后会收到GetChnImgSize回调来获取宽高信息

>RegionCreateAll   这个函数用于创建所有odf区域,不同厂商的处理可能会有不同,RegionCreateAll需要将region句柄赋值给region list的id,这个region句柄具体的生成方式由芯片厂商自行定义,后续的RegionDestroyAllSetBitMap以及ImageToCanvas等回调都会携带这个region句柄来标识身份

10. Ai

弃用MediaServer集成Ai模块,使用独立模块AiPlugin,以库的形式给到MediaServer调用,请参考文档《EmxAiPlugin对接手册》

11. Vo[todo]

12. Draw

  Draw模块实现线框的叠加,目前支持的有三种类型,分别是直线,矩形和椭圆

需要实现的接口:
ErrCodeE OnCreate() = 0;
void OnDestroy() = 0;
Size GetChnPicSize() = 0;
virtual void DrawCreateAll(std::list<DrawInfo> &info) = 0;
virtual void DrawSetItem(const int id, ParamItem &item) = 0;
virtual void DrawDestroyAll(std::list<DrawInfo> &info) = 0;

>DrawCreateAll

  这个函数用于创建所有画线框,不同厂商的处理可能会有不同,要注意item参数判断type类型,每一个item都需要赋值id

>DrawSetItem

  这个函数用于修改指定句柄iditem属性,不同厂商的处理可能会有不同

>DrawDestroyAll

  这个函数用于释放所有的画线框,可以根据指定id处理

13. Aov

  Aov,即Always on Video,实现视频始终在线(即视频不间断)的功能。AOV技术基于超低功耗内存的快速启动待机技术,通过在一定时间段内(如30秒)抓拍一次图片(通常以1fps的帧率),并分析图像中是否存在特定目标(如人、车等)。一旦检测到目标,摄像机即切换到正常工作模式,进行全帧率录像和实时视频流传输。若未检测到目标,则继续保持在低功耗待机状态。   Aov由模式mode和唤醒间隔intervalS两个参数控制   如果不支持此模块可以忽略实现或者返回nullptr

需要实现的接口:
virtual ErrCodeE OnCreate() = 0;
virtual void OnDestroy() = 0;
virtual ErrCodeE OnParamChanged() = 0;