手搓ZLMediaKit实现拉流推流系列1(C++版)

        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.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.7 main函数

2. 编译

2.1 编译ZLmediaKit

2.2 编译自己源程序的CMakeLists.txt


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库
)

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不想起名字呢

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值