MediaClient

media的用户层调用接口,应用开发使用的接口。 更多...

MediaClient 的协作图:

class  Emx::MediaAdec
 音频编码 更多...
 
class  Emx::MediaAdecStreamSync
 音频流解码接口,均为同步阻塞操作 更多...
 
class  Emx::MediaAdecStreamSync2
 音频流解码接口,均为同步阻塞操作,分离了创建-发送-销毁,相比于MediaAdecStreamSync减少频繁调用下的性能开销 更多...
 
class  Emx::MediaAdecStreamAsync
 音频流解码异步接口,均为异步操作 更多...
 
class  Emx::MediaAenc
 音频编码 更多...
 
class  Emx::MediaAi
 AI 更多...
 
class  Emx::MediaAiDataRecvBase
 AI数据接收基类 更多...
 
class  Emx::MediaAiDataRecvAsync
 异步AI数据接收 更多...
 
class  Emx::MediaAiDataRecvSync
 同步AI数据接收 更多...
 
class  Emx::MediaAov
 视频输出 更多...
 
class  Emx::MediaAovMessage
 异步Aov消息数据接收 更多...
 
class  Emx::MediaDraw
 Draw 更多...
 
class  Emx::MediaDrawAsync
 Draw 动态配置异步接口,均为异步操作 更多...
 
class  Emx::MediaIsp
 Isp 更多...
 
class  Emx::MediaIspInfo
 异步ISPInfo数据接收 更多...
 
class  Emx::MediaOsd
 OSD 更多...
 
class  Emx::MediaParamCBaseImpl
 
class  Emx::MediaParamCBase< Param >
 客户端参数基类,方便通用操作 更多...
 
class  Emx::MediaQrScan
 二维码扫描 更多...
 
class  Emx::MediaSnap
 图像抓拍 更多...
 
class  Emx::MediaStream
 MediaFrame数据接收基类 更多...
 
class  Emx::MediaStreamAsync
 MediaFrame异步数据接收 更多...
 
class  Emx::MediaStreamSync
 MediaFrame同步数据接收 更多...
 
class  Emx::MediaSystem
 控制媒体服务整体的启停 更多...
 
class  Emx::MediaVdec
 视频解码类,实现视频解码相关功能。 更多...
 
class  Emx::MediaVdecStream
 同步视频解码流类,用于处理视频解码流的同步操作。 更多...
 
class  Emx::MediaVdecStreamAsync
 异步视频解码流类,用于处理视频解码流的异步操作。 更多...
 
class  Emx::MediaVenc
 视频编码 更多...
 
class  Emx::MediaVi
 视频输入 更多...
 
class  Emx::MediaVo
 视频输出 更多...
 

详细描述

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的状态信息,这些信息主要是供用户进行日夜切换的软光敏阈值判断。

// ISP状态信息监听示例
#include "EmxMedia.hpp"
using namespace Emx;
class ListenIsp {
public:
void Create() {
m_loop.Init("ListenIsp", [this]() { OnQuit(); });
// 启动并注册一个回调函数,当接收到Isp状态信息后触发
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对象
//创建
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 = {};
// 定义一个通道0的编码器对象
MediaClientVenc venc(0);
// 先获取当前的配置
if (venc.GetParam(param) != ErrCodeE::Success) {
emxloge("GetParam failed\n");
return;
}
// 切换编码格式
if (param.codec == Emx::VideoCodecE::H265)
param.codec = VideoCodecE::H264;
else
param.codec = Emx::VideoCodecE::H265;
if (venc.SetParam(param) != ErrCodeE::Success) {
emxloge("SetParam failed\n");
return;
}
emxlogi("SetParam success\n");
}
// 刷新I帧
void FlushIDR(int chn) {
// 定义一个通道0的编码器对象
MediaClientVenc venc(chn);
venc.FlushIDR();
}
};
@ H265
H265编码
@ H264
H264编码
@ Success
成功

4.4. 音频解码

  Emx提供两种类型的解码,一种是文件解码播放,一种是实时流解码播放,其中实时流的解码需要满足带解码音频流的编码格式与通道解码配置参数一致,文件解码播放支持aac/wav和pcm格式,需要采样率,通道数与解码通道配置参数一致,编码类型可与通道解码配置参数不一致

播放音频文件示例
#include "EmxMedia.hpp"
using namespace Emx;
// 播放音频文件示例
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;
}
// 播放指定目录下的音频文件,可以多次重复调用,MediaServer会按照先后顺序组织播放队列进行播放
return 0;
}
static ErrCodeE PlayFile(int32_t chn, const char *path, int32_t timeoutMs=1000)
添加音频文件路径至媒体服务的播放列表中,添加后此函数就会返回,列表为空时会立刻播放

解码播放实时音频流示例
#include "EmxMedia.hpp"
using namespace Emx;
// 解码播放实时音频流示例
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("usage:./TalkbackPlay the/path/to/music.aac\n");
return 0;
}
// 打开aac音频文件,需要保证解码通道配置为aac解码,并且与music文件解码配置一致
FILE *fp = fopen(argv[1], "rb");
if (!fp) {
emxloge("cannot open file %s\n", argv[1]);
return -1;
}
char buffer[2048];
while (true) {
// 读取aac音频文件
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;
}
// 将音频帧发送给MediaServer进行解码播放
MediaAdec::Frame frame = {};
frame.data = (uint8_t *) buffer;
frame.size = (int) size;
// 防止读文件的方式发送实时流太快造成缓冲区溢出,这里增加延时
usleep(20000);
}
return 0;
}
static ErrCodeE PushFrame(int32_t chn, MediaAdec::Frame &frame, int32_t timeoutMs=1000)
发送音频帧到媒体服务进行解码播放,这里发送的音频帧的编码格式需要与MediaClientAenc中配置的解码格式一致

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"
using namespace Emx;
// 二维码扫码示例
int main(int argc, char *argv[]) {
EuvLoop loop;
char buffer[512];
// 创建一个QR扫描时间句柄,监听通道sensor0的扫码
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"
using namespace Emx;
// 缩略图抓取示例
int main() {
// 抓拍一张图片
// 添加需要监听的Snap通道,这里监听snap通道0
MediaStream::Channel channel;
channel.Add(MediaFrame::TypeE::Snap, 0);
// 启动snap的监听
MediaStreamSync stream;
ErrCodeE e = stream.Open(channel, 3000);
if (e != ErrCodeE::Success) {
emxloge("open snap failed\n");
return -1;
}
// 触发一个snap 通道0的信号,MediaServer收到这个信号后会立刻发送一帧缩略图
MediaSnap snap(0);
snap.Trigger(3000);
// 接收这帧缩略图
MediaFrame frame = {};
e = stream.GetFrame(frame, 3000);
if (e != ErrCodeE::Success) {
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);
// 关闭snap接收通道
stream.Close();
return 0;
}
ErrCodeE
错误码定义
Definition: EmxTypeDef.hpp:29

4.8. 音视频实时流

  音视频实时流的获取(包括snap)是通过MediaStream实现,包括同步获取和异步获取,两种流程基本一致,添加需要获取的通道类型和通道号-监听或打开流通道-获取音视频-关闭或停止通道。其中异步获取是通过绑定EuvLoop,将媒体帧的到达当作一个事件触发,然后将媒体帧投喂给用户注册的回调函数。同步的方式是用户自己开一个线程,使用阻塞超时的方式等待媒体帧的到来,然后进行处理。

异步实时流获取示例
#include "EmxMedia.hpp"
using namespace Emx;
// 异步实时流获取示例
class SaveAsyncStream {
public:
// MediaStreamAsync
SaveAsyncStream() {
m_fpVenc = nullptr;
m_fpAenc = nullptr;
}
void Create(EuvLoop &loop, char *buffer, int size) {
// 首先定义通道,并向通道中加入需要监听的媒体帧类型
MediaStream::Channel channel;
channel.Add(Emx::MediaFrame::TypeE::Venc, 0); // 加入视频编码通道0
channel.Add(Emx::MediaFrame::TypeE::Aenc, 0); // 加入音频编码通道0
// 注册当获取到媒体帧时候的回调
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;
}
//这里只会收到m_stream.Start的时候在channel中Add的帧类型+通道的帧
if (frame.type == Emx::MediaFrame::TypeE::Venc) {
// 收到视频帧
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指针用来存储编码后的视频裸流
FILE *m_fpAenc; // 定义file指针用来存储编码后的音频裸流
};
int main() {
EuvLoop loop; // 定义一个loop用于所有事件的监听
SaveAsyncStream demo; // 定义demo对象
// loop取名为SaveAsyncStream,当loop销毁时执行demo.Destroy()
loop.Init("SaveAsyncStream", [&]() { demo.Destroy(); }, Emx::EuvLoop::Dynamic);
//创建
char buffer[1024];
demo.Create(loop, buffer, (int) sizeof(buffer));
// loop启动
loop.Start();
printf("input 'q' to exit\n");
while ('q' != getchar());
// 此处会出发loop.Init的时候注册的回调,并执行其中的demo.Destroy()
loop.StopAndDeInit();
printf("exit success\n");
return 0;
}
@ Dynamic
动态loop模式,此模式下会动态的malloc一个loop
Definition: EuvLoop.hpp:27

同步实时流获取示例
#include "EmxMedia.hpp"
#include <thread>
using namespace Emx;
// 同步实时流获取示例
class SaveSyncStream {
public:
void Create() {
m_quit = false;
m_thread = std::thread([this]() {
FILE *fpVenc = fopen("video.raw", "wb+");// 定义file指针用来存储编码后的视频裸流
FILE *fpAenc = fopen("audio.raw", "wb+");// 定义file指针用来存储编码后的音频裸流
// 首先定义通道,并向通道中加入需要监听的媒体帧类型
MediaStream::Channel channel;
channel.Add(Emx::MediaFrame::TypeE::Venc, 0); // 加入视频编码通道0
channel.Add(Emx::MediaFrame::TypeE::Aenc, 0); // 加入音频编码通道0
MediaStreamSync stream;
stream.Open(channel, 1000);
while (!m_quit) {
MediaFrame frame = {};
auto e = stream.GetFrame(frame, 1000);
continue;
//这里只会收到m_stream.Start的时候在channel中Add的帧类型+通道的帧
if (frame.type == Emx::MediaFrame::TypeE::Venc) {
// 收到视频帧
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对象
//创建
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"
using namespace Emx;
// 异步AI消息获取示例
class Ai {
public:
void Create(EuvLoop &loop) {
// AI的获取不需要配置监听类型和通道,默认监听所有通道
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;
if (m_ai.GetDataByInfo(header, &data) != ErrCodeE::Success)
return;
// 遍历所有数据,例如:如果检测到运动面积大于10%,就认为是移动事件
// 另外基于data中的x/y/w/h以及header中的srcPicSize,可以用来定位事件在画面中的位置
// 可以用来做移动追踪或者检测区域的过滤
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;
}
// 处理人行侦测消息
if (m_ai.GetDataByInfo(header, &data) != ErrCodeE::Success)
return;
// 遍历所有数据,例如:如果检测到运动面积大于10%,就认为是人形事件
// 另外基于data中的x/y/w/h以及header中的srcPicSize,可以用来定位事件在画面中的位置
// 可以用来做移动追踪或者检测区域的过滤
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; // 定义一个loop用于所有事件的监听
Ai demo = {}; // 定义demo对象
// loop取名为Ai,当loop销毁时执行demo.Destroy()
loop.Init("Ai", [&]() { demo.Destroy(); }, Emx::EuvLoop::Dynamic);
//创建
demo.Create(loop);
// loop启动
loop.Start();
printf("input 'q' to exit\n");
while ('q' != getchar());
// 此处会出发loop.Init的时候注册的回调,并执行其中的demo.Destroy()
loop.StopAndDeInit();
printf("exit success\n");
return 0;
}
@ MotionDetection
移动侦测数据
@ PersonDetection
人形侦测数据
MediaInfAiDataMotionDetection MediaInfAiDataPersonDetection
Definition: MediaInfAiDataPersonDetection.hpp:14