2023-5-26
关键字
rk3568,arm64,linux,zlmediakit交叉编译,zlmediakit实现rtsp服务
一、ZLMediaKit交叉编译
aarch64交叉编译
下载ZLMediaKit源码,并在程序主目录下的CMakeLists.txt中,追加交叉编译配置项。
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(target_arch aarch64-linux-gnu)
#这可替换为需要的编译工具路径
set(CMAKE_C_COMPILER /usr/bin/aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER /usr/bin/aarch64-linux-gnu-g++)
set(CMAKE_LIBRARY_ARCHITECTURE ${target_arch} CACHE STRING "" FORCE)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
#添加交叉编译时需要的目标平台头,库文件路径
include_directories (/opt/sysroot/firefly-arm64-sysroot-18.04/usr/include/aarch64-linux-gnu
/opt/sysroot/firefly-arm64-sysroot-18.04/usr/include
../../mbedtls-2.2.1/include)
link_directories (/opt/sysroot/firefly-arm64-sysroot-18.04/usr/lib/aarch64-linux-gnu
/opt/sysroot/firefly-arm64-sysroot-18.04/lib
/opt/sysroot/firefly-arm64-sysroot-18.04/usr/lib)
ZLMediaKit加入工程
交叉编译完成后,安装ZLMediaKit到临时目录下;将libmk_api.so
库文件和mk_
开头的文件添加到应用。
二、ZLMeidaKit功能梳理
-
zlmediakit提供的api接口功能
zlmediakit能很方便地创建rtspServer,但是需要自己设计网络事件响应,实现视频的推流。api接口可以很方便实现播放,停止,倍速等基础功能;同时要注意在无终端访问时需关闭媒体资源。 -
客户端请求播放地址规则
1)产生的rtsp请求url必须是二级地址,要注意其字段含义:参看: 播放url规则
2)blw-gy3-nvr产品中,采用字段格式:rtsp://[ipaddr]/live/video?FilePath=[fileName]
[ipaddr]
:填写nvr本地可用有效ip地址。这个字段,将由mds查询nvr视频时,nvr提供。如rtsp://192.168.111.1/live/video?FilePath=
。
[fileName]
:填写文件名称,不能填写绝对路径,nvr将根据文件名称搜索数据库得到对应的文件路径。如填写:Ch1_2023-04-03-10-49-53.ts
。
3)完整url例子:rtsp://192.168.111.1/live/video?FilePath=Ch1_2023-04-03-10-49-53.ts
-
events监听:
rtsp服务主要注册两个事件:on_mk_media_not_found
,on_mk_media_no_reader
。
on_mk_media_not_found:主要负责对媒体资源对象的申请和配置。
on_mk_media_no_reader:主要负责在没有播放客户端时,关闭现有的媒体资源对象。 -
媒体对象
主要是使用mk_media, mk_h264_splitter
这两个对象。zlmediakit api将申请的对象采用链表的形式进行管理。
mk_media
对象可以提供播放资源url。是操作核心对象。
mk_h264_splitter
对象则负责处理数据帧。也必不可少。 -
暂停,倍速,时间戳功能
这些功能可以向媒体资源(mk_media
)进行注册回调函数:
mk_media_set_on_pause, mk_media_set_on_speed, mk_media_set_on_seek
三、程序设计
1. 服务器注册
方法:将zlmediakit的服务功能放到一个线程中提供。利用zlmediakit api函数进行注册和配置相关服务器功能,注册服务器响应事件。
成员准备
/* 成员数据准备 */
typedef struct {
AVFormatContext *pFormatCtx; /* AVFormatContext上下文 */
AVPacket *packet; /* AVPacket */
int videoStream; /* 视频流下标 */
int fps; /* 每秒帧数 */
int64_t cur_pts; /* 当前播放位置 */
int pause; /* 暂停控制位 */
int speed; /* 速度控制位 */
}ffmpegObj;
typedef struct {
mk_media media; /* 媒体对象 */
mk_h264_splitter splitter; /* 分帧对象 */
char absFilePath[512]; /* 文件绝对路径 */
pthread_t spliterThread; /* 分帧处理线程. 目的:ffmpeg从文件中获取packet传给媒体对象进行处理 */
pthread_mutex_t t_mutex; /* 互斥锁 */
bool splitFlag; /* 分帧线程运行标志 */
char schema[10]; /* 服务类型 */
}zlMediaSource;
服务器初始化
void zlmedia_api::server()
{
/* 环境配置 */
mk_config config;
config.thread_num = 1;
config.log_level = 4;
config.log_mask = LOG_CONSOLE;
config.log_file_path = NULL;
config.log_file_days = 7;
config.ini_is_path = 1;
config.ini = NULL;
config.ssl_is_path = 0;
config.ssl = NULL;
config.ssl_pwd = NULL;
mk_env_init(&config);
/* RTSP服务器申请 */
mk_rtsp_server_start(554, 0);
/* server服务器事件响应 */
mk_events events = {
.on_mk_media_changed = NULL,
.on_mk_media_publish = NULL,
.on_mk_media_play = NULL,
.on_mk_media_not_found = on_mk_media_not_found,
.on_mk_media_no_reader = on_mk_media_no_reader,
.on_mk_http_request = NULL,
.on_mk_http_access = NULL,
.on_mk_http_before_access = NULL,
.on_mk_rtsp_get_realm = NULL,
.on_mk_rtsp_auth = NULL,
.on_mk_record_mp4 = NULL,
.on_mk_shell_login = NULL,
.on_mk_flow_report = NULL,
};
mk_events_listen(&events);
/* ffmpeg协议注册 */
avformat_network_init();
/* 数据库连接 */
this->initDb();
/* 成员初始化 */
this->Ctx.spliterThread = 0;
this->Ctx.splitFlag = false;
memset(this->Ctx.absFilePath, '0', sizeof(this->Ctx.absFilePath));
memset(this->Ctx.schema, '0', sizeof(this->Ctx.schema));
this->ffmpegObject.videoStream = -1;
this->ffmpegObject.speed = 1;
this->ffmpegObject.pause = 0;
/* 服务器前缀 , 传递给mds的 */
AppData::RtspURLPrefix = QString("rtsp://%1/live/video?FilePath=").arg(hostIP().toString());
qDebug() << "zlmediakit url: " << AppData::RtspURLPrefix << "[filename]";
/* 规定时间内无访问客户终端,释放媒体对象及资源 */
mk_set_option("general.streamNoneReaderDelayMS", "1000");
}
2.事件监听
on_mk_media_not_found
用于注册mk_media
对象,并提供播放服务。
1.on_mk_media_not_found响应
/* on_mk_media_not_found响应 */
int API_CALL on_mk_media_not_found(const mk_media_info url_info,
const mk_sock_info sender) {
zlmedia_api::Instance()->mediaSourceCreate(url_info);
return 0;
}
2.媒体资源申请
/* 申请/响应逻辑 */
void zlmedia_api::mediaSourceCreate(void *user_data)
{
mk_media_info url_info = (mk_media_info)user_data;
const char* schema = mk_media_info_get_schema(url_info);
const char* app = mk_media_info_get_app(url_info);
const char* prama = mk_media_info_get_params(url_info);
/* 从url的FilePath参数获取文件名称 */
QString strPrama(prama);
if(!strPrama.startsWith("FilePath="))
return;
QStringList strListParam = strPrama.split('=');
if(strListParam.size() != 2)
return;
QString filePath = strListParam.last();
if(filePath.length() <= 0)
return;
/* 从数据库查找文件完整位置 */
QString absFilePath = this->dbSearchRecord(filePath);
if(absFilePath.isEmpty())
return;
const char *specFilePath = absFilePath.toLatin1().data();
if(!QFile::exists(absFilePath)){
qDebug() << "zlmediakit : file is not exist";
goto fault_missing_file;
}
/* 申请媒体资源 */
if(this->getStopStatus()) {
if(this->mediaPublish(schema, app, specFilePath) < 0)
goto fault_open_file;
}
else {
if(strncmp(specFilePath, this->Ctx.absFilePath, strlen(specFilePath)) != 0 ||
strncmp(schema, this->Ctx.schema, strlen(schema)) != 0) {
this->stopSpliterThread();
if(this->mediaPublish(schema, app, specFilePath) < 0)
goto fault_open_file;
}
} /* end: if(this->getStopStatus())*/
return ;
fault_open_file:
fault_missing_file:
return ;
}
/*
1.mk_media, mk_h264_splitter资源创建,并指定播放url 。
2.打开文件并配置相关内容。
*/
int zlmedia_api::mediaPublish(const char* schema, const char* app, const char* specFilePath)
{
strcpy(this->Ctx.schema, schema);
strcpy(this->Ctx.absFilePath, specFilePath);
/* 总时长 */
int64_t duration = 0;
/* openFile函数内部将打开this->Ctx.absFilePath存储的文件,
* 获取AVFormatContext句柄,
* 返回文件总时长
*/
if(this->openFile(&duration) < 0) {
qDebug() << "ffmpeg can't open file";
return -1;
}
/* 总时长转换 */
duration /= AV_TIME_BASE;
/* mk_media 媒体资源创建,并且指定了播放url */
this->Ctx.media = mk_media_create("__defaultVhost__", "live", "video", duration, 0, 0);
codec_args v_args = {0};
mk_track v_track = mk_track_create(MKCodecH264, &v_args);
mk_media_init_track(this->Ctx.media, v_track);
mk_media_init_complete(this->Ctx.media);
/* 暂停,倍速,时间戳功能注册 */
//mk_media_set_on_regist(this->Ctx.media, on_mk_media_source_regist_func, this);
mk_media_set_on_pause(this->Ctx.media, mk_media_pause, this);
mk_media_set_on_speed(this->Ctx.media, mk_media_speed, this);
mk_media_set_on_seek(this->Ctx.media, mk_media_seek, this);
mk_track_unref(v_track);
/* 分帧器创建,必须的 */
this->Ctx.splitter = mk_h264_splitter_create(on_h264_frame, this, 0);
/* ffmpeg处理线程 */
this->Ctx.splitFlag = true;
this->initPthreadMutex();
pthread_create(&this->Ctx.spliterThread, NULL, splitter_input_calls, this);
return 0;
}
3.openFile函数获取ffmpeg相关资源
int zlmedia_api::openFile(int64_t *duration)
{
int err_code;
/* 重新申请上下文句柄 */
if(this->ffmpegObject.pFormatCtx != NULL) {
avformat_close_input(&this->ffmpegObject.pFormatCtx);
this->ffmpegObject.pFormatCtx = NULL;
}
this->ffmpegObject.pFormatCtx = avformat_alloc_context();
if (this->ffmpegObject.pFormatCtx == NULL) {
qDebug() << "zlmedia: failed to alloc format context.";
return -1;
}
/* 重新申请AVPackte */
if(this->ffmpegObject.packet != NULL) {
av_packet_free(&this->ffmpegObject.packet);
this->ffmpegObject.packet = NULL;
}
this->ffmpegObject.packet = av_packet_alloc();
if(this->ffmpegObject.packet == NULL){
qDebug() << "zlmedia: failed to alloc packet.";
avformat_close_input(&this->ffmpegObject.pFormatCtx);
return -1;
}
/* 获取文件操作上下文句柄 */
err_code = avformat_open_input(&this->ffmpegObject.pFormatCtx, this->Ctx.absFilePath, nullptr, nullptr);
if(err_code < 0){
qDebug() << "av_open_input_file failed:" << err_code;
avformat_close_input(&this->ffmpegObject.pFormatCtx);
av_free_packet(this->ffmpegObject.packet);
return -1;
}
if(avformat_find_stream_info(this->ffmpegObject.pFormatCtx, NULL) < 0)
{
avformat_close_input(&this->ffmpegObject.pFormatCtx);
avformat_free_context(this->ffmpegObject.pFormatCtx);
av_free_packet(this->ffmpegObject.packet);
return -1;
}
/* 获取文件视频流下标和帧率 */
for(int i = 0; i < this->ffmpegObject.pFormatCtx->nb_streams; i++)
{
if(this->ffmpegObject.pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
this->ffmpegObject.videoStream = i;
this->ffmpegObject.fps = av_q2d(this->ffmpegObject.pFormatCtx->streams[i]->r_frame_rate);
break;
}
}
if(this->ffmpegObject.videoStream < 0) {
return -1;
}
/* 获取总时长 */
*duration = this->ffmpegObject.pFormatCtx->duration;
return 0;
}
4.分帧器处理回调函数
必须要将视频Packet获取及传递给媒体资源对象的这个过程放置在子线程中执行,否则会阻塞服务线程,导致无法继续开展事件处理。
//pthread_create(&this->Ctx.spliterThread, NULL, splitter_input_calls, this);
void* splitter_input_calls(void *data) {
zlmedia_api *zlmedia = (zlmedia_api *)data;
AVFormatContext *pFormatCtx =zlmedia->ffmpegObject.pFormatCtx;
int i_videoIndex = zlmedia->ffmpegObject.videoStream;
AVStream *stream = zlmedia->ffmpegObject.pFormatCtx->streams[i_videoIndex];
AVPacket *packet = zlmedia->ffmpegObject.packet;
int fps = zlmedia->ffmpegObject.fps;
int *speed = &zlmedia->ffmpegObject.speed;
mk_media ctx_media = zlmedia->Ctx.media;
mk_h264_splitter splitter = zlmedia->Ctx.splitter;
while(zlmedia->Ctx.splitFlag)
{
/* 暂停控制 */
if(zlmedia->ffmpegObject.pause) {
pthread_mutex_unlock(&zlmedia->Ctx.t_mutex);
continue;
}
/* 有效数据控制 */
if(av_read_frame(pFormatCtx, packet) < 0)
break;
/* 当前播放位置 */
uint64_t dts = (packet->pts * av_q2d(stream->time_base)) *1000;
/* 数据载入间隔 */
int interval = (int)((1000.0 / ( fps * (*speed) )) * 1000.0);
/* 数据传递给媒体资源对象和分帧对象处理 */
if(packet->stream_index == i_videoIndex)
{
try {
/* mk_h264_splitter_input_data
* 函数调用,将会把buf和size提交给h264_splitter回调函数on_h264_frame,
* 但是,实际提交的数据量和发送的数据量不同,导致最后会丢失部分视频。
* 因此直接在这里提交数据给媒体资源对象。
*
* 同时该input函数会管理4MB缓存,有溢出风险,导致程序崩溃。
* 暂没找到,故不知道使用什么接口,在查看/释放该缓存。
*/
//mk_h264_splitter_input_data(splitter, (char *)packet->data, packet->size);
usleep(interval);
mk_frame frame = mk_frame_create(MKCodecH264, dts, dts, (char *)packet->data, packet->size, NULL, NULL);
mk_media_input_frame(ctx_media, frame);
mk_frame_unref(frame);
}
catch(...) {
qDebug() << QDateTime::currentDateTime() << "Err ZlmeidaApi: mk_h264_splitter_input_data";
}
}
/* 释放AVPacket引用 */
av_packet_unref(packet);
} /* end: while */
/* 释放媒体资源和ffmpeg操作对象 */
zlmedia->ffmpegObjFree();
zlmedia->mediaRelease();
pthread_exit((void *)0);
}
5.媒体资源释放
void zlmedia_api::ffmpegObjFree()
{
if(this->ffmpegObject.pFormatCtx != NULL) {
avformat_close_input(&this->ffmpegObject.pFormatCtx);
this->ffmpegObject.pFormatCtx = NULL;
}
if(this->ffmpegObject.packet != NULL) {
av_packet_free(&this->ffmpegObject.packet);
this->ffmpegObject.packet = NULL;
}
this->ffmpegObject.videoStream = -1;
this->ffmpegObject.fps = 0;
this->ffmpegObject.cur_pts = 0;
this->ffmpegObject.speed = 1;
this->ffmpegObject.pause = 0;
}
void zlmedia_api::mediaRelease()
{
if(this->Ctx.media != NULL) {
mk_media_release(this->Ctx.media);
this->Ctx.media = NULL;
}
if(this->Ctx.splitter != NULL) {
mk_h264_splitter_release(this->Ctx.splitter);
this->Ctx.splitter = NULL;
}
memset(this->Ctx.absFilePath, '0', sizeof(this->Ctx.absFilePath));
memset(this->Ctx.schema, '0', sizeof(this->Ctx.schema));
this->Ctx.splitFlag = false;
}
6.暂停,倍速,时间戳功能
//创建mk_media对象时设置回调函数
//mk_media_set_on_pause(this->Ctx.media, mk_media_pause, this);
//mk_media_set_on_speed(this->Ctx.media, mk_media_speed, this);
//mk_media_set_on_seek(this->Ctx.media, mk_media_seek, this);
static int mk_media_pause(void* user_data, int pause)
{
zlmedia_api *zlmedia = (zlmedia_api *)user_data;
zlmedia->ffmpegObject.pause = pause;
return 0;
}
int mk_media_speed(void* user_data, float speed)
{
if(speed > 32)
return -1;
zlmedia_api *zlmedia = (zlmedia_api *)user_data;
zlmedia->ffmpegObject.speed = (int)speed;
return 0;
}