Python RK3588 MPP硬解码!全体目光向我看齐!

        在进行图像相关的算法工作时,通常离不开拉取实时摄像头进行拉流工作。在python中,通常使用OPENCV完成解码工作,但OPENCV解码非常占用CPU资源,对于资源受限的边缘设备,占用大量CPU资源显然是我们不愿意看到的。

        好在Rockchip官方针对自家的开发版设计了专门的硬件解码芯片,并通过mpp进行硬件解码。不过官方提供的是基于C++开发的示例,着实令没接触过C++的朋友们头疼。

        因此,本文重写了官方demo、加入了Zlmediakit进行拉流并通过pybind11将代码封装成so库,现在,在python中直接通过import的方式,就能完成基础的拉流解码操作!!!

 废话不多说,先上一个简单的演示:

output

        在上述演示中,我写了一个简单的 test.py 用于调用 mpp_decoder 进行拉流以及解码操作,代码如下:

rtsp_stress = ""

import cv2
import time
import mpp_decoder
# args1: rtsp address, args2: video type (support h264, h265)
mpp_decoder.start_processing_async(rtsp_stress, 264)

frame_idx = 0
while True:
    frame = mpp_decoder.get_decoded_frame()
    if frame.size > 0:
        converted_image = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
        
        cv2.imwrite(f"./output/frame_{frame_idx}.jpg", converted_image)
        print(f"Received frame with shape: {converted_image.shape}")
        frame_idx += 1
    else:
        print("No frame available")
        time.sleep(1)
    time.sleep(0.1)
  •         使用pybind11封装c++代码思路:

                1. start_processing_async函数接收 rtsp 流地址以及流类型

                2. 调用 process_video_rtsp 函数使用 Zlmediakit 进行拉流操做

                3. Zlmediakit 在监听事件on_track_frame_out进行解码操作:ctx->decoder->Decode()

                4. 解码一帧数据后触发回调函数 mpp_decoder_frame_callback

                5. 回调函数 mpp_decoder_frame_callback 负责取出数据、放入大小为50的缓冲区

                6. python 通过调用 get_decoded_frame 函数取出数据,get_decoded_frame 函数中负责将数据转换成 python numpy 的数据类型并返回给 python

  •         具体代码如下:
void mpp_decoder_frame_callback(void *userdata, int width_stride, int height_stride, int width, int height, int format, int fd, void *data) 
{  
    rga_buffer_t origin; 
    std::vector<size_t> shape = { (size_t)height, (size_t)width, 3 };
    std::vector<size_t> strides = { (size_t)width * 3, (size_t)3, 1 };
    
    // 创建 YUV420SP 格式的缓冲区
    origin = wrapbuffer_fd(fd, width, height, RK_FORMAT_YCbCr_420_SP, width_stride, height_stride);
    
    cv::Mat origin_mat = cv::Mat::zeros(height, width, CV_8UC3);
    rga_buffer_t rgb_img = wrapbuffer_virtualaddr((void *)origin_mat.data, width, height, RK_FORMAT_RGB_888);
    imcopy(origin, rgb_img);

    // 将解码后的数据存入共享缓冲区
    std::lock_guard<std::mutex> lock(buffer_mutex);  
    if (frame_buffer.size() >= 50) 
    {         
        frame_buffer.pop();                          // 丢弃旧帧
    }
    frame_buffer.push(origin_mat);                   // 存储新帧
}

// Python 端从缓冲区读取数据的接口
py::array_t<uint8_t> get_decoded_frame() 
{
    std::lock_guard<std::mutex> lock(buffer_mutex);  
    if (frame_buffer.empty()) 
    {
        return py::array_t<uint8_t>();               // 如果没有数据则返回空数组
    }
    cv::Mat frame = frame_buffer.front();
    frame_buffer.pop();
    // 将 cv::Mat 转换为 NumPy 数组
    py::array_t<uint8_t> result = py::array_t<uint8_t>(
        {frame.rows, frame.cols, 3},               
        {frame.cols * 3, 3, 1},                    
        frame.data                               
    );
    return result;
}


void API_CALL on_track_frame_out(void *user_data, mk_frame frame) {
    rknn_zl_app_context_t *ctx = (rknn_zl_app_context_t *) user_data;
    // printf("on_track_frame_out ctx=%p\n", ctx);
    const char *data = mk_frame_get_data(frame);    // 获取帧数据指针
    ctx->dts = mk_frame_get_dts(frame);             // 获取解码时间戳,单位毫秒
    ctx->pts = mk_frame_get_pts(frame);             // 获取显示时间戳,单位毫秒
    size_t size = mk_frame_get_data_size(frame);    // 获取帧数据指针长度
    // printf("decoder=%p\n", ctx->decoder);
    
    ctx->decoder->Decode((uint8_t *)data, size, 0); // 调用decoder解码器进行解码
}


void API_CALL on_mk_play_event_func(void *user_data, int err_code, const char *err_msg, mk_track tracks[], int track_count) 
{
    rknn_zl_app_context_t *ctx = (rknn_zl_app_context_t *) user_data; // 将 user_data 转换成 Zlmediakit Context 结构体指针
    if (err_code == 0) 
    {
        log_debug("play success!");
        int i;
        for (i = 0; i < track_count; ++i) 
        {
            if (mk_track_is_video(tracks[i]))
            {
                // 监听 frame 的输出事件,参数:当前音视频轨道, 回调函数on_track_frame_out,用户数据指针
                mk_track_add_delegate(tracks[i], on_track_frame_out, user_data);
            }
        }
    } 
    else 
    {
        log_warn("play failed: %d %s", err_code, err_msg);
    }
}


int process_video_rtsp(rknn_zl_app_context_t *ctx, const char *url)
{
    mk_config config;                               // zlmediakit config配置,默认即可
    memset(&config, 0, sizeof(mk_config));
    config.log_mask = LOG_CONSOLE;                  // 设置日志输出的路径
    mk_env_init(&config);                           // 使用 config 配置初始化 zlmediakit 环境
    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);
    while(true)
    {
        std::this_thread::sleep_for(std::chrono::seconds(10));   
    }
}


void start_processing_async(const std::string& rtsp_address, int video_type) 
{
    std::thread([rtsp_address, video_type]() 
    {
        rknn_zl_app_context_t app_ctx;
        memset(&app_ctx, 0, sizeof(rknn_zl_app_context_t));

        if (app_ctx.decoder == NULL) 
        {
            MppDecoder* decoder = new MppDecoder();
            decoder->Init(video_type, 30, &app_ctx);
            decoder->SetCallback(mpp_decoder_frame_callback);
            app_ctx.decoder = decoder;
        }

        // 使用 std::string 的 c_str(),确保 rtsp_address 在线程内是有效的
        process_video_rtsp(&app_ctx, rtsp_address.c_str());

        if (app_ctx.decoder != nullptr) 
        {
            delete app_ctx.decoder;
            app_ctx.decoder = nullptr;
        }
    }).detach();  // 分离线程,让它后台执行
}

PYBIND11_MODULE(mpp_decoder, m) 
{
    // 绑定 start_processing_async 函数
    m.def("start_processing_async", [](const std::string &rtsp_address, int video_type){
        start_processing_async(rtsp_address, video_type);
    }, "Start processing RTSP stream with specified URL and video type");

    // 绑定 get_decoded_frame 函数
    m.def("get

        上述所有代码都运行在docker容器中,在docker容器中只需要export 一下so库的路径即可。

export PYTHONPATH=/mnt/mpp_decoder/build:$PYTHONPATH

    目前仅支持单摄像头拉流,如果问题,欢迎评论私信!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

不想起名字呢

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

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

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

打赏作者

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

抵扣说明:

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

余额充值