关键词: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的易用性,为多媒体处理领域提供了一种高效的解决方案。