【DLL】基于Python与C++的FFmpeg与OpenCV实践

367 阅读5分钟

关键词:DLL、SO、 C++、 PYTHON、FFMPEG、OPENCV

一. 引言

  在多媒体处理领域,FFmpeg与OpenCV是两款不可或缺的工具。FFmpeg作为跨平台的多媒体处理框架,能够轻松实现音视频的编码、解码、转码等操作;而OpenCV作为计算机视觉领域的首选库,提供了丰富的图像处理与算法实现。本文将介绍如何将FFmpeg与OpenCV结合使用,基于C++实现视频处理功能,并通过Python进行封装调用。

基于C++的FFmpeg推流实现

  在C++项目中,这里我参考了github开源库 进行二次修改开发。首先需要引入FFmpeg的头文件与库文件。这里需要注意的是,FFmpeg的跨平台编译与链接可能会遇到不少问题。我选择通过静态库的方式进行集成,这样可以避免动态库在不同环境下的兼容性问题。

#include "utils.h"  // 引入工具头文件,包含FFmpeg和OpenCV相关功能的封装

// 初始化FFmpeg的格式上下文、编码器、流等对象
AVFormatContext* ofmt_ctx = nullptr;  // 输出格式上下文
const AVCodec* out_codec = nullptr;  // 输出编码器
AVStream* out_stream = nullptr;  // 输出流
AVCodecContext* out_codec_ctx = nullptr;  // 输出编码器上下文
AVFrame* frame = nullptr; // 初始化帧缓冲区
SwsContext* swsctx = nullptr; // 初始化图像缩放上下文


extern "C" __declspec(dllexport)  void init_ffmpeg(int fps, int width, int height, int bitrate, const char* codec_profile, const char* output)
{
// 根据FFmpeg版本,注册所有可用的文件格式和编解码器
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 9, 100)
    av_register_all();
#endif
    avformat_network_init();  // 初始化网络相关功能
    initialize_avformat_context(ofmt_ctx, "flv");// 初始化输出格式上下文,指定输出格式为FLV
    initialize_io_context(ofmt_ctx, output);// 初始化IO上下文,用于输出到指定的RTMP地址
    out_codec = avcodec_find_encoder(AV_CODEC_ID_H264);// 查找H.264编码器
    out_stream = avformat_new_stream(ofmt_ctx, out_codec);// 创建一个新的输出流,并将其与编码器关联
    out_codec_ctx = avcodec_alloc_context3(out_codec); // 分配编码器上下文
    set_codec_params(ofmt_ctx, out_codec_ctx, width, height, fps, bitrate); // 设置编码器的参数,如分辨率、帧率、码率等
    initialize_codec_stream(out_stream, out_codec_ctx, out_codec, codec_profile);// 初始化编码器流,包括设置编码器选项(如配置文件、预设等)
    out_stream->codecpar->extradata = out_codec_ctx->extradata;// 将编码器上下文的额外数据(如SPS、PPS)复制到流的参数中
    out_stream->codecpar->extradata_size = out_codec_ctx->extradata_size;
    av_dump_format(ofmt_ctx, 0, output, 1);// 打印输出流的格式信息
    swsctx = initialize_sample_scaler(out_codec_ctx, width, height);// 初始化图像缩放上下文,用于将输入图像转换为编码器支持的格式
    frame = allocate_frame_buffer(out_codec_ctx, width, height); // 分配一个帧缓冲区,用于存储编码器的输入数据
    int ret = avformat_write_header(ofmt_ctx, nullptr);// 写入流的文件头信息
    if (ret < 0)
    {
        printf("Could not write header! \n");
        exit(1);
    }
}

extern "C" __declspec(dllexport)  void push_ffmpeg(unsigned char* image_data, const int stride[], int image_rows) {
    sws_scale(swsctx, &image_data, stride, 0, image_rows, frame->data, frame->linesize);// 使用sws_scale函数将图像从BGR格式转换为编码器支持的YUV420P格式
    frame->pts += av_rescale_q(1, out_codec_ctx->time_base, out_stream->time_base);// 更新帧的时间戳
    write_frame(out_codec_ctx, ofmt_ctx, frame);// 将帧发送到编码器,并将编码后的数据写入输出流
}

extern "C" __declspec(dllexport)  void close_ffmpeg() {
    // 释放相关资源
    av_frame_free(&frame);
    avcodec_close(out_codec_ctx);
    avio_close(ofmt_ctx->pb);
    avformat_free_context(ofmt_ctx);
    // 初始化资源置空
    ofmt_ctx = nullptr;
    out_codec = nullptr;
    out_stream = nullptr;
    frame = nullptr; // 
    swsctx = nullptr; // 
}



int main()
{
    int fps = 25;  // 帧率
    int width = 1920;  // 视频宽度
    int height = 1080;  // 视频高度
    int bitrate = 600000;  // 码率
    const char* codec_profile = "high444";  // 编码器配置文件
    const char* output = "rtmp://localhost/live/stream";  // 输出流地址,这里是一个RTMP服务器地址
    init_ffmpeg(fps, width, height, bitrate, codec_profile, output); // 初始化ffmepg相应参数
    

    // 使用OpenCV打开一个视频文件
    cv::VideoCapture video;  // 用VideoCapture来读取视频
    cv::Mat image;           // 声明一个保存图像的类
    video.open("E:/ffmpeg_exey/bin/20s.mp4");  // 打开指定的视频文件
    if (!video.isOpened())   // 判断视频是否打开成功
    {
        printf("open video error! \n");
        exit(0);
    } 

    do 
    {
        video >> image;  // 读取一帧
        if (image.empty())  // 如果读取到空帧,说明视频结束
            break;
        const int stride[] = { static_cast<int>(image.step[0]) };  // 计算图像的步长
        push_ffmpeg(image.data, stride, image.rows); // 输入图像的指针和高
    } while (video.isOpened());
    
    video.release();  // 释放opencv视频资源
    close_ffmpeg(); // 释放ffmpeg

    return 0;
}

Python调用的DLL封装

  为了方便用户使用,我们将C++实现的功能封装成DLL动态链接库,并通过Python进行调用。通过这种方式,可以在Python环境中轻松调用C++实现的FFmpeg推流与OpenCV图像处理功能,实现高效的多媒体处理流程,避开了使用python的管道通信实现多线程操作带来的资源消耗;

import cv2
import ctypes


class ingest_lib(object):
    def __init__(self, dll_path,
                 fps=25,
                 width=1920,
                 height=1080,
                 bitrate=600000,
                 codec_profile=b"high444",
                 output=b"rtmp://localhost/live/stream"):
        self._fps = fps
        self._width = width
        self._height = height
        self._bitrate = bitrate
        self._codec_profile = codec_profile
        self._output = output
        self.ffmpeg_dll = ctypes.CDLL(dll_path)  # 替换为DLL文件的实际路径
        # 定义函数参数类型和返回类型
        self.ffmpeg_dll.init_ffmpeg.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_char_p,
                                                ctypes.c_char_p]
        self.ffmpeg_dll.init_ffmpeg.restype = None

        self.ffmpeg_dll.push_ffmpeg.argtypes = [ctypes.POINTER(ctypes.c_ubyte), ctypes.POINTER(ctypes.c_int),
                                                ctypes.c_int]
        self.ffmpeg_dll.push_ffmpeg.restype = None

        self.ffmpeg_dll.close_ffmpeg.argtypes = []
        self.ffmpeg_dll.close_ffmpeg.restype = None

    def init_ffmpeg(self):
        self.ffmpeg_dll.init_ffmpeg(self._fps, self._width, self._height, self._bitrate, self._codec_profile,
                                    self._output)

    def close_ffmpeg(self):
        self.ffmpeg_dll.close_ffmpeg()

    def push_ffmpeg(self, frame):
        # 将OpenCV的图像数据转换为适合传递给DLL的格式
        image_data = frame.ctypes.data_as(ctypes.POINTER(ctypes.c_ubyte))  # 获取图像数据的指针
        stride = (ctypes.c_int * 1)(frame.strides[0])  # 获取图像的步长
        image_rows = frame.shape[0]  # 获取图像的行数

        # 调用DLL进行推流
        self.ffmpeg_dll.push_ffmpeg(image_data, stride, image_rows)


if __name__ == '__main__':
    # 初始化FFmpeg参数
    dll_path = r"C:\Users\kiven\Desktop\FFmpegOpencv\PythonLoadDLL\PushFFmpeg.dll"
    fps = 25
    width = 1920
    height = 1080
    bitrate = 600000
    codec_profile = b"high444"  # 注意:字符串需要以字节串形式传递
    output = b"rtmp://localhost/live/stream"  # 替换为你的RTMP服务器地址

    ingest_ = ingest_lib(dll_path, fps, width, height, bitrate, codec_profile, output)
    ingest_.init_ffmpeg()
    # 使用OpenCV打开视频文件
    video = cv2.VideoCapture(r"E:\ffmpeg_exey\bin\20s.mp4")  # 替换为视频文件的实际路径
    if not video.isOpened():
        print("Open video error!")
        exit(0)

    while True:
        ret, frame = video.read()
        if not ret:
            break
        ingest_.push_ffmpeg(frame)

    # 释放资源
    video.release()
    ingest_.close_ffmpeg()

总结

  通过本次实践,成功实现了基于C++的FFmpeg推流功能与OpenCV图像处理功能,并通过Python进行封装调用。这种方式充分发挥了C++的高性能与Python的易用性,为多媒体处理领域提供了一种高效的解决方案。