4.1. 系统控制
系统控制部分提供了EmxMediaServer功能的启动和停止,当EmxMediaServer程序运行后会默认启动所有媒体服务,当用户需要固件升级等需要大量内存空间时,可以调用此接口临时停止媒体服务使其释放出内存空间,对应的接口是MediaSystem::CreateMedia和MediaSystem::DestroyMedia 另外,EmxMediaServer提出了scene的概念,每个scene是一套完整参数和配置的集合,主要考虑到某些IPC产品需要动态的切换使用场景,此时可能会涉及到大量参数和配置的改变,例如双目产品从双目拼接输出切换到双目独立输出的场景,双目拼接和双目独立的两套配置文件和参数文件分别存储在各自的scene下面,通过MediaSystem::SwitchScene接口可以实现scene的切换。当然普通的IPC产品一般只有一个默认的scene就足够了,EmxMediaServer启动时会去自动加载默认scene下的配置参数。
4.2. 图像ISP
EmxMediaServer启动后会自动加载ISP的参数和配置文件,完成自身的初始化创建。ISP部分主要实现了3部分功能,一.运行ISP后台动态调节功能,这部分是Server内部实现的,无对外控制接口。二.提供设备ISP参数的配置和获取以及夜视白天的状态切换,ISP的参数配置是通过MediaIsp类提供的接口实现,可实现曝光,锐度,白平衡等诸多参数的动态配置,以及日夜模式的切换。三.ISP实时状态的获取,这部分是通过MediaIspInfo类提供,当注册相应的回调之后即可不停的获取到ISP的状态信息,这些信息主要是供用户进行日夜切换的软光敏阈值判断。
#include "EmxMedia.hpp"
class ListenIsp {
public:
void Create() {
m_loop.Init("ListenIsp", [this]() { OnQuit(); });
m_isp.Start(m_loop, m_buffer, sizeof(m_buffer), [this](MediaIsp::Info &info) {
emxlogd("chn=%d, luma=%d, iso=%d, redGain=%d, blueGain=%d\n",
info.chn, info.luma, info.iso, info.redGain, info.blueGain);
});
m_loop.Start();
}
void Destroy() {
m_loop.StopAndDeInit();
}
private:
void OnQuit() {
m_isp.Stop();
}
private:
EuvLoop m_loop;
char m_buffer[4096];
MediaIspInfo m_isp;
};
int main() {
ListenIsp demo;
demo.Create();
printf("input 'q' to exit\n");
while ('q' != getchar());
demo.Destroy();
printf("exit success\n");
return 0;
}
Definition: EmxGpio.hpp:10
4.3. 音视频编码
EmxMediaServer启动后会自动加载aenc和venc的参数和配置文件,完成自身的初始化创建,编码参数和码流数量都是由配置文件定义好的。音视频编码部分仅提供编码器的配置,实时码流的获取参考3.9.音视频实时流
视频编码配置示例,音频/ISP等使用SetParam/GetParam接口进行参数配置的类都可参考此示例
class DemoVencConfig {
public:
void Configs() {
MediaVenc::Param param = {};
MediaClientVenc venc(0);
if (venc.GetParam(param) != ErrCodeE::Success) {
emxloge("GetParam failed\n");
return;
}
param.codec = VideoCodecE::H264;
else
if (venc.SetParam(param) != ErrCodeE::Success) {
emxloge("SetParam failed\n");
return;
}
emxlogi("SetParam success\n");
}
void FlushIDR(int chn) {
MediaClientVenc venc(chn);
venc.FlushIDR();
}
};
4.4. 音频解码
Emx提供两种类型的解码,一种是文件解码播放,一种是实时流解码播放,其中实时流的解码需要满足带解码音频流的编码格式与通道解码配置参数一致,文件解码播放支持aac/wav和pcm格式,需要采样率,通道数与解码通道配置参数一致,编码类型可与通道解码配置参数不一致
播放音频文件示例
#include "EmxMedia.hpp"
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("usage:./DemoAudioFilePlay the/path/to/music.aac\n");
printf("support format .aac/.wav(codec=alaw/ulaw/pcm)/.pcm\n");
return 0;
}
return 0;
}
解码播放实时音频流示例
#include "EmxMedia.hpp"
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("usage:./TalkbackPlay the/path/to/music.aac\n");
return 0;
}
FILE *fp = fopen(argv[1], "rb");
if (!fp) {
emxloge("cannot open file %s\n", argv[1]);
return -1;
}
char buffer[2048];
while (true) {
auto n = fread(buffer, 1, 7, fp);
if (n <= 0) {
fseek(fp, SEEK_SET, 0);
emxlogd("EOF\n");
break;
}
auto size = ((buffer[3] & 0x03) << 11) | (buffer[4] << 3) | (buffer[5] >> 5);
n = fread(buffer + 7, 1, size - 7, fp);
if ((int) n != size - 7) {
emxloge("Not an ADTS packet\n");
return -1;
}
MediaAdec::Frame frame = {};
frame.data = (uint8_t *) buffer;
frame.size = (int) size;
usleep(20000);
}
return 0;
}
4.5. OSD
Emx提供了三种类型的OSD叠加,分别是时间戳显示,文字显示和图片显示。三种OSD都支持对齐布局,可以支持水平左对齐/右对齐/居中对齐以及垂直顶部对齐/底部对齐/居中对齐。并且对齐之后也可以进行对齐后的偏移操作,偏移是按照比例进行的,逻辑上将画面切分成了1000份,每个单位偏移值相当于偏移了画面的1/1000,这部分操作针对的是MediaOsd::Margin结构体,详细可参考MediaInfOsd.hpp。文字类的时间戳显示和文字显示支持字体选择与字体大小配置,以及字体描边配置。 针对时间戳OSD,Emx支持显示类型的调节,例如"2022-05-20 12:24:48"或者"2022-05-20 星期五 12:24:48" 针对文字OSD,目前支持的最大字符数量是MediaOsd::MaxTextSize字节 详细可参考MediaInfOsd.hpp
4.6. 二维码扫描
Emx支普通镜头与鱼眼镜头的二维码扫描,当用户使用MediaQrScan绑定loop后,MediaQrScan会监听是否有有效的二维码被解析出来,解析成功后会通过注册的回调将结果通知出来,解析成功后MediaQrScan并不会自动停止,需要用户手动关闭。
二维码扫码示例
#include "EmxMedia.hpp"
int main(int argc, char *argv[]) {
EuvLoop loop;
char buffer[512];
MediaQrScan *qr = new MediaQrScan(0);
m_loop.Init("MediaQrScan", [&]() {
qr->StopQR();
delete qr;
});
qr->StartQR(loop, buffer, sizeof(buffer), [](int chn, const char *result){
emxlogi("chn %d get QR content#%s#\n", chn, result);
});
loop.Start();
printf("input 'q' to exit\n");
while ('q' != getchar());
loop.StopAndDeInit();
printf("exit success\n");
}
4.7. 缩略图抓拍
缩略图抓拍是使用了与实时流相同的接口,需要经历监听-触发-收取-关闭这几个过程,首先是创建针对snap通道的监听,这里同样可以使用同步或者异步的方式,然后利用MediaSnap::Trigger告知EmxMediaServer这里需要一张缩略图,EmxMediaServer收到请求后会编码一张缩略图并发送到ringbuffer中,这时监听回调会被触发,然后接收图片并关闭监听通道
缩略图抓取示例
#include "EmxMedia.hpp"
int main() {
MediaStream::Channel channel;
MediaStreamSync stream;
ErrCodeE e = stream.Open(channel, 3000);
emxloge("open snap failed\n");
return -1;
}
MediaSnap snap(0);
snap.Trigger(3000);
MediaFrame frame = {};
e = stream.GetFrame(frame, 3000);
emxloge("get snap failed\n");
return -1;
}
FILE *fp = fopen("./snap.jpg", "wb+");
if (!fp) {
emxloge("open snap failed\n");
stream.Close();
return -1;
}
fwrite(frame.data, 1, frame.size, fp);
stream.Close();
return 0;
}
ErrCodeE
错误码定义
Definition: EmxTypeDef.hpp:29
4.8. 音视频实时流
音视频实时流的获取(包括snap)是通过MediaStream实现,包括同步获取和异步获取,两种流程基本一致,添加需要获取的通道类型和通道号-监听或打开流通道-获取音视频-关闭或停止通道。其中异步获取是通过绑定EuvLoop,将媒体帧的到达当作一个事件触发,然后将媒体帧投喂给用户注册的回调函数。同步的方式是用户自己开一个线程,使用阻塞超时的方式等待媒体帧的到来,然后进行处理。
异步实时流获取示例
#include "EmxMedia.hpp"
class SaveAsyncStream {
public:
SaveAsyncStream() {
m_fpVenc = nullptr;
m_fpAenc = nullptr;
}
void Create(EuvLoop &loop, char *buffer, int size) {
MediaStream::Channel channel;
m_stream.Start(loop, buffer, size, channel, std::bind(&SaveAsyncStream::OnRecvFrame, this, ph_1, ph_2));
m_fpVenc = fopen("video.raw", "wb+");
m_fpAenc = fopen("audio.raw", "wb+");
}
void Destroy() {
m_stream.Stop();
if (m_fpVenc) {
fclose(m_fpVenc);
m_fpVenc = nullptr;
}
if (m_fpAenc) {
fclose(m_fpAenc);
m_fpAenc = nullptr;
}
}
private:
void OnRecvFrame(
ErrCodeE e, MediaFrame &frame) {
emxlogw("recv stream failed\n");
return;
}
fwrite(frame.data, 1, frame.size, m_fpVenc);
} else {
fwrite(frame.data, 1, frame.size, m_fpAenc);
}
}
private:
MediaStreamAsync m_stream;
FILE *m_fpVenc;
FILE *m_fpAenc;
};
int main() {
EuvLoop loop;
SaveAsyncStream demo;
char buffer[1024];
demo.Create(loop, buffer, (int) sizeof(buffer));
loop.Start();
printf("input 'q' to exit\n");
while ('q' != getchar());
loop.StopAndDeInit();
printf("exit success\n");
return 0;
}
@ Dynamic
动态loop模式,此模式下会动态的malloc一个loop
Definition: EuvLoop.hpp:27
同步实时流获取示例
#include "EmxMedia.hpp"
#include <thread>
class SaveSyncStream {
public:
void Create() {
m_quit = false;
m_thread = std::thread([this]() {
FILE *fpVenc = fopen("video.raw", "wb+");
FILE *fpAenc = fopen("audio.raw", "wb+");
MediaStream::Channel channel;
MediaStreamSync stream;
stream.Open(channel, 1000);
while (!m_quit) {
MediaFrame frame = {};
auto e = stream.GetFrame(frame, 1000);
continue;
fwrite(frame.data, 1, frame.size, fpVenc);
} else {
fwrite(frame.data, 1, frame.size, fpAenc);
}
}
stream.Close();
fclose(fpVenc);
fclose(fpAenc);
});
}
void Destroy() {
m_quit = true;
m_thread.join();
}
private:
std::thread m_thread;
bool m_quit;
};
int main() {
SaveSyncStream demo;
demo.Create();
printf("input 'q' to exit\n");
while ('q' != getchar());
demo.Destroy();
printf("exit success\n");
return 0;
}
4.9. 智能AI
智能AI部分提供了用户配置和获取AI数据的功能,用户可以对不同种类AI的使能和参数进行配置,并通过MediaAiDataRecvBase获取这些AI的数据。AI数据的获取分为同步和异步两种方式。
示例
#include "EmxMedia.hpp"
public:
void Create(EuvLoop &loop) {
m_ai.Start(&loop, m_buffer, (int) sizeof(m_buffer), std::bind(&Ai::ProcAiData, this, ph_1));
}
void Destroy() {
m_ai.Stop();
}
private:
void ProcAiData(MediaInfAiData::Header &header) {
switch (header.type) {
MediaInfAiDataMotionDetection data;
return;
for (int i = 0; i < data.num; i++) {
if (data.array[i].Area() > header.srcPicSize.w * header.srcPicSize.h * 10 / 100) {
emxlogi("motion alarm triggered\n");
return;
}
}
return;
}
return;
for (int i = 0; i < data.num; i++) {
if (data.array[i].Area() > header.srcPicSize.w * header.srcPicSize.h * 10 / 100) {
emxlogi("person alarm triggered\n");
return;
}
}
return;
}
case MediaInfAiData::TypeE::FaceSnap:
break;
default:
break;
}
}
private:
MediaAiDataRecvAsync m_ai;
char m_buffer[256];
};
int main() {
EuvLoop loop;
demo.Create(loop);
loop.Start();
printf("input 'q' to exit\n");
while ('q' != getchar());
loop.StopAndDeInit();
printf("exit success\n");
return 0;
}