ZLMediakit 作为一款强大且开源的流媒体服务框架,个人认为已经非常的成熟且使用。本文只是在官方源码的基础上进行微调并添加上了注释内容,感兴趣的同学可以去官网看看源代码以及使用文档:https://github.com/ZLMediaKit/ZLMediaKit
让我们步入本篇文章的正题,本篇文章作为手搓ZLMediaKit系列的入门版,旨在帮助和自己一样的小白玩家熟悉Zlmediakit从RTSP进行拉流并推流到RTMP服务器的一个过程。下述贴的代码合起来即为完整版代码,实际使用时只需要在main函数中添加上RTSP和RTMP地址即可。
后续打算在源码的基础上进行修改,添加Rockchip MPP硬件编解码模块。(已实现,地址传送门:手搓ZLMediakit实现推流拉流系列2(Rockchip MPP编解码))
在系列3之前,如果你还不清楚RKNN C++ API 的使用或者你想抽象出自己的RKNN推理框架,或许你可以参考下面这篇文章:
手搓ZLMediakit实现推流拉流系列扩展(RKNN C++推理流程)
其实还打算,在解码之后编码之前加点深度学习推理内容进去。(暂时定位系列3吧)
目录
1.1 Zlmediakit Context定义与各资源释放函数
1.2 基础配置与拉流控制函数 process_video_rtsp
1.3 播放器player正常播放/播放中断的回调函数 on_mk_play_event_func
1.4 音/视频轨道帧(track frame)输出回调函数 on_track_frame_out
1.5 媒体源注册事件回调函数 on_mk_media_source_regist_func
1.6 推流器pusher正常推流/推流失败回调函数on_mk_push_event_func
1.函数解释
1.1 Zlmediakit Context定义与各资源释放函数
这些资源释放函数主要用来管理各类资源,如:media,player,pusher等。倒也没啥需要修改添加的内容,直接copy过来就行。
Context作为上下文环境信息,可以根据自己的需要添加一些变量进去,后续进行MPP硬件编解码时会对这部分进行扩展。
// ---------- Zlmediakit context定义 ----------
typedef struct {
mk_player player; // 播放器
mk_media media; // 媒体服务
mk_pusher pusher; // 推送器
char push_url[1024]; // 推送地址
} Context;
// ---------- 释放媒体资源服务 media 的函数 ----------
void release_media(mk_media *ptr) {
if (ptr && *ptr) {
mk_media_release(*ptr);
*ptr = NULL;
}
}
// ---------- 释放播放器 palyer 的函数 ----------
void release_player(mk_player *ptr) {
if (ptr && *ptr) {
mk_player_release(*ptr);
*ptr = NULL;
}
}
// ---------- 释放推流器 pusher 的函数 ----------
void release_pusher(mk_pusher *ptr) {
if (ptr && *ptr) {
mk_pusher_release(*ptr);
*ptr = NULL;
}
}
// ---------- 释放上下文资源 context 的函数,调用此函数会释放puser、 media、 player ----------
void release_context(Context **ptr){
if (ptr && *ptr) {
release_pusher(&(*ptr)->pusher);
release_media(&(*ptr)->media);
release_player(&(*ptr)->player);
free(*ptr);
*ptr = NULL;
}
}
1.2 基础配置与拉流控制函数 process_video_rtsp
基础配置也没啥可以修改的,把URL换成自己的RTSP和RTMP源地址就行。
int process_video_rtsp(Context *ctx, const char *url, const char *push_url)
{
release_player(&(ctx->player)); // 释放之前的 player
/* --------------- 设置zlmediakit config配置,并使用config配置初始化zlmediakit --------------- */
mk_config config; // zlmediakit config配置,默认即可
memset(&config, 0, sizeof(mk_config));
config.log_mask = LOG_CONSOLE; // 设置日志输出的路径
mk_env_init(&config); // 使用 config 配置初始化 zlmediakit 环境
/*-------------------------------------------
启动RTSP/RTMP服务器并设置播放器
mk_rtsp_server_start---@param 1: rtsp监听端口,推荐554,传入0则随机分配;@param 2: 是否是ssl类型服务器
mk_rtmp_server_start---@param 1: rtmp监听端口,推荐1935,传入0则随机分配; @param 2: 是否是ssl类型服务器
-------------------------------------------*/
mk_rtsp_server_start(554, 0);
mk_rtmp_server_start(1935, 0);
mk_player player = mk_player_create();
//设置播放器正常播放/播放中断的回调函数
mk_player_set_on_result(player, on_mk_play_event_func, ctx);
mk_player_set_on_shutdown(player, on_mk_play_event_func, ctx);
// 开始播放 url
mk_player_play(player, url);
// 保存推流地址到 zlmediakit 上下文中
strcpy(ctx->push_url, push_url);
// 中断拉流推流流程
printf("enter any key to exit\n");
getchar();
if (player)
{
mk_player_release(player);
}
return 0;
}
1.3 播放器player正常播放/播放中断的回调函数 on_mk_play_event_func
正常/播放中断的回调函数,其中需要注意的就是mk_media_create函数的后3个参数,具体解释注释里面就有,不多赘述了,注意0是代表True。
void API_CALL on_mk_play_event_func(void *user_data, int err_code, const char *err_msg, mk_track tracks[], int track_count) {
Context *ctx = (Context *) user_data; // 将 user_data 转换成 Zlmediakit Context 结构体指针
release_media(&(ctx->media)); // 释放之前的 media
release_pusher(&(ctx->pusher)); // 释放之前的 pusher
// error_code = 0 表示正常播放
if (err_code == 0) {
log_debug("play success!");
// 创建一个媒体源,并设置 虚拟主机名、应用名、流id、时长(直播为0),是否生成hls、是否生成mp4
ctx->media = mk_media_create("__defaultVhost__", "live", "test", 0, 0, 0);
int i;
for (i = 0; i < track_count; ++i) {
// 添加音频轨道(track),参数:媒体源对象,当前音视频轨道
mk_media_init_track(ctx->media, tracks[i]);
// 监听 frame 的输出事件,参数:当前音视频轨道, 回调函数on_track_frame_out,用户数据指针
mk_track_add_delegate(tracks[i], on_track_frame_out, user_data);
}
// 初始化 h264/h265/aac 完毕后调用此函数,参数:媒体源对象
mk_media_init_complete(ctx->media);
// 设置MediaSource注册或注销事件回调函数,参数:媒体源对象,回调函数on_mk_media_source_regist_func,用户数据指针
mk_media_set_on_regist(ctx->media, on_mk_media_source_regist_func, ctx);
} else {
log_warn("play failed: %d %s", err_code, err_msg);
}
}
1.4 音/视频轨道帧(track frame)输出回调函数 on_track_frame_out
这算是一个比较重要的函数,同时也比较简单,主要就是把监听到的frame放到用户定义的Context中的media里面。
void API_CALL on_track_frame_out(void *user_data, mk_frame frame) {
Context *ctx = (Context *) user_data;
// 向用户数据中的media输入frame对象
mk_media_input_frame(ctx->media, frame);
}
1.5 媒体源注册事件回调函数 on_mk_media_source_regist_func
其实倒也不用讲这么高大上的名字,简单来说,就是推流函数。
void API_CALL on_mk_media_source_regist_func(void *user_data, mk_media_source sender, int regist){
Context *ctx = (Context *) user_data;
const char *schema = mk_media_source_get_schema(sender); // 获取媒体源的协议
// 检查协议类型是否与推流地址的协议相匹配
if (strncmp(schema, ctx->push_url, strlen(schema)) == 0) {
release_pusher(&(ctx->pusher)); // 释放之前的推流器资源
if (regist) {
// 如果是注册事件,则创建推流器并发布
ctx->pusher = mk_pusher_create_src(sender);
// 设置推流器正常推流/推流中断回调函数
mk_pusher_set_on_result(ctx->pusher, on_mk_push_event_func, ctx);
mk_pusher_set_on_shutdown(ctx->pusher, on_mk_push_event_func, ctx);
// 发布推流
mk_pusher_publish(ctx->pusher, ctx->push_url);
} else {
log_info("push stoped!");
}
}
else
{
printf("unknown schema:%s\n", schema);
}
}
1.6 推流器pusher正常推流/推流失败回调函数on_mk_push_event_func
简单来说,就是记录推流状态的一个函数。
void API_CALL on_mk_push_event_func(void *user_data,int err_code,const char *err_msg){
Context *ctx = (Context *) user_data;
if (err_code == 0) {
// 推流成功
log_info("push %s success!", ctx->push_url);
} else {
// 推流失败,释放推流器资源
log_warn("push %s failed:%d %s", ctx->push_url, err_code, err_msg);
release_pusher(&(ctx->pusher));
}
}
1.7 main函数
把stream_url 和 push_url 换成自己的即可。
int main() {
char *stream_url = "rtsp://";
char *push_url = "rtmp://";
// ---------- 分配并初始化 Zlmediakit Context ----------
Context *ctx = (Context *) malloc(sizeof(Context));
memset(ctx, 0, sizeof(Context));
process_video_rtsp(ctx, stream_url, push_url);
release_context(&ctx);
}
2. 编译
2.1 编译ZLmediaKit
git clone https://github.com/enpeizhao/ZLMediaKit
# ----------安装缺的组件----------
git submodule init
git submodule update
# ----------编译ZLMediaKit,可能需要10分钟----------
cmake . -B build && cmake --build build
# ----------将编译好的so库移到自己创建的目录中,比如我自己定义的mpp_libs
cp ./submodules/ZLMediaKit/release/linux/Debug/libmk_api.so mpp_libs/
2.2 编译自己源程序的CMakeLists.txt
注意:本CMakeLists.txt 是在linux系统上编译的,编译之前清先确定你的系统然后修改库架构部分。
# 设置最低版本号
cmake_minimum_required(VERSION 3.11 FATAL_ERROR)
# 设置项目名称
project(zlmediakit-demo VERSION 0.0.1 LANGUAGES CXX)
# 输出系统信息
message(STATUS "System: ${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_VERSION}")
# 设置编译器
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 设置库架构
set(LIB_ARCH "aarch64") # 注意,我是在linux系统上编译的,编译之前清先确定你的系统
# 链接库目录
link_directories(
mpp_libs
)
# 用来搜索头文件的目录
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/src
${CMAKE_CURRENT_SOURCE_DIR}/submodules/ZLMediaKit/api/include # 注意根据自己的位置进行替换
)
add_executable(zlmediakit_pull_push_test
src/zlmediakit_pull_push_test.cpp # 创建可执行文件
)
target_link_libraries(zlmediakit_pull_push_test
mk_api # 为可执行文件链接zlmediakit库
)