VS+cuda+ffmpeg实现相机实时采集进行视频压缩

一、环境配置:VS2022、cuda12.4、basler库、opencv3.4.16库、ffmpeg7.0.1库

经过多次测试,实现了相机采集实时压缩视频,代码应该是比较精简的,可用于工程中。

其中cuda12.4、opencv3.4.16、ffmpeg7.0.1大家可以自己找一下教程配置一下。

相机的话我用的是basler相机测试的,大家在相机采集这一块,可以根据自己的相机型号进行修改,相机配好后,上面那些库也配置一下,就可以了。真的想用的话就自己好好配一下相机库跟相机采集代码的一些东西,只要相机配好了,其他应该是没问题的。

二、这个代码我已经多次测试,测试情况如下:

该demo实现了从内存图片数据到视频的压缩方法,采用了ffmpeg的接口。

1、加装cuda前:测试30fps,3000张,压缩的视频大小为42.7M,视频时长99秒,程序运行时长131秒。
2、加装cuda后:
测试内容:相机采集图像,将图像实时压缩并合成视频。
测试相机:basler,500万像素。测试30fps,采集3000张图片,压缩的视频大小为34.8M,视频时长99秒,程序运行时长99秒,已达到实时压缩视频。

3、运行期间CPU,GPU情况:

cpu:i7-14700KF,占用率7%~10%,平均8%左右。

gpu:RTX4070,占用率11%~16%,平均13%左右。
 

extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/imgutils.h>
#include <libavutil/opt.h>
#include <libswscale/swscale.h>
}
#include "CPylonBasler.h"
#include <string>
#include <time.h>
#include <functional>
#include <opencv2/opencv.hpp>
#include <iomanip>
#include <cstdlib>
#include <iostream>
#include <vector>
#include <nvjpeg.h>
#include <chrono>
#include <mutex>
#include <thread>
#include <condition_variable>
#include <atomic>
#include <queue>
#include <cuda_runtime.h>
#include <pylon/PylonIncludes.h>
#include <device_launch_parameters.h>

//本程序经过测试,已达到实时将图像采集并合成压缩视频的效果,运用了cuda12.4,ffmpeg进行加速。
std::mutex frame_mutex;
std::condition_variable frame_cond;
std::atomic<bool> capturing(true);
std::queue<cv::Mat> frame_queue;

void initialize_ffmpeg() {
    avformat_network_init(); // 初始化网络库,如果不需要网络流,可以省略此行
}

AVFormatContext* create_format_context(const char* filename) {
    AVFormatContext* format_context = nullptr;
    avformat_alloc_output_context2(&format_context, nullptr, nullptr, filename);
    if (!format_context) {
        std::cerr << "无法分配格式上下文" << std::endl;
        return nullptr;
    }
    return format_context;
}

AVStream* create_video_stream(AVFormatContext* format_context, AVCodecID codec_id, int width, int height, int fps, AVCodecContext** codec_context) {
    const AVCodec* codec = avcodec_find_encoder(codec_id);
    if (!codec) {
        std::cerr << "找不到编码器" << std::endl;
        return nullptr;
    }

    AVStream* stream = avformat_new_stream(format_context, codec);
    if (!stream) {
        std::cerr << "无法创建视频流" << std::endl;
        return nullptr;
    }

    *codec_context = avcodec_alloc_context3(codec);
    if (!*codec_context) {
        std::cerr << "无法分配编码器上下文" << std::endl;
        return nullptr;
    }

    (*codec_context)->codec_id = codec_id;
    (*codec_context)->codec_type = AVMEDIA_TYPE_VIDEO;
    (*codec_context)->width = width;
    (*codec_context)->height = height;
    (*codec_context)->time_base = { 1, fps };
    (*codec_context)->pix_fmt = AV_PIX_FMT_YUV420P;
    (*codec_context)->gop_size = 10;
    (*codec_context)->max_b_frames = 1;

    if (format_context->oformat->flags & AVFMT_GLOBALHEADER)
        (*codec_context)->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

    if (avcodec_open2(*codec_context, codec, nullptr) < 0) {
        std::cerr << "无法打开编码器" << std::endl;
        return nullptr;
    }

    stream->codecpar->codec_id = codec_id;
    stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
    stream->codecpar->width = width;
    stream->codecpar->height = height;
    stream->codecpar->format = (*codec_context)->pix_fmt;

    if (avcodec_parameters_from_context(stream->codecpar, *codec_context) < 0) {
        std::cerr << "无法从编码器上下文拷贝参数到视频流" << std::endl;
        return nullptr;
    }

    return stream;
}

void write_frame(AVFormatContext* format_context, AVStream* stream, AVCodecContext* codec_context, AVFrame* frame, int frame_index) {
    frame->pts = frame_index;

    AVPacket* packet = av_packet_alloc();
    if (!packet) {
        std::cerr << "无法分配AVPacket" << std::endl;
        return;
    }

    int ret = avcodec_send_frame(codec_context, frame);
    if (ret < 0) {
        std::cerr << "发送帧时出错: " << ret << std::endl;
        av_packet_free(&packet);
        return;
    }

    while (ret >= 0) {
        ret = avcodec_receive_packet(codec_context, packet);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
            break;
        }
        else if (ret < 0) {
            std::cerr << "编码帧时出错: " << ret << std::endl;
            break;
        }

        packet->stream_index = stream->index;
        packet->pts = av_rescale_q(packet->pts, codec_context->time_base, stream->time_base);
        packet->dts = av_rescale_q(packet->dts, codec_context->time_base, stream->time_base);
        packet->duration = av_rescale_q(packet->duration, codec_context->time_base, stream->time_base);

        ret = av_interleaved_write_frame(format_context, packet);
        if (ret < 0) {
            std::cerr << "写入帧时出错: " << ret << std::endl;
            break;
        }

        av_packet_unref(packet);
    }

    av_packet_free(&packet);
}
//cuda的核函数,CUDA内核将灰度图像转换为YUV420P格式
__global__ void gray_to_yuv420p_kernel(const unsigned char* gray_img, unsigned char* y_plane, unsigned char* u_plane, unsigned char* v_plane, int width, int height) {
    int x = blockIdx.x * blockDim.x + threadIdx.x;
    int y = blockIdx.y * blockDim.y + threadIdx.y;

    if (x < width && y < height) {
        int gray_index = y * width + x;
        int y_index = y * width + x;

        y_plane[y_index] = gray_img[gray_index];

        if (x % 2 == 0 && y % 2 == 0) {
            int uv_index = (y / 2) * (width / 2) + (x / 2);
            u_plane[uv_index] = 128;
            v_plane[uv_index] = 128;
        }
    }
}

void capture_frames(CPylonBasler& camera, int width, int height, int fps, int num_frames) {
    auto start_time = std::chrono::steady_clock::now();
    for (int i = 0; i < num_frames && capturing; ++i) {
        camera.SofwareTrigger();
        camera.SaveCapturedImage(); // 将相机采集的图像数据保存到m_pImgBuffer中

        cv::Mat gray_img(cv::Size(width, height), CV_8UC1, camera.m_pImgBuffer);

        {
            std::lock_guard<std::mutex> lock(frame_mutex);
            frame_queue.push(gray_img.clone());
        }
        frame_cond.notify_one();

        auto elapsed_time = std::chrono::steady_clock::now() - start_time;
        auto sleep_time = std::chrono::milliseconds(1000 / fps) * (i + 1) - elapsed_time;
        if (sleep_time > std::chrono::milliseconds(0)) {
            std::this_thread::sleep_for(sleep_time);
        }
    }
    capturing = false;
    frame_cond.notify_one();
}

int main() {
    // 记录程序开始时间
    auto program_start_time = std::chrono::steady_clock::now();

    const char* filename = "30.mp4";
    const AVCodecID codec_id = AV_CODEC_ID_H264;
    const int fps = 30;
    const int num_frames = 1000;
    const int width = 2448;// 根据实际相机输出分辨率调整
    const int height = 2048;

    // 初始化相机(根据实际相机库调整)
    CPylonBasler camera;
    camera.ConnectCamera();
    camera.SetAllCamParams();
    camera.SetCamTriggerMode(TRIGGER_SOFTWARE);
    camera.baslerCamera.StartGrabbing();
    // 初始化FFmpeg
    initialize_ffmpeg();

    // 创建输出格式上下文和视频流
    AVCodecContext* codec_context = nullptr;
    AVFormatContext* format_context = create_format_context(filename);
    if (!format_context)
        return -1;

    AVStream* video_stream = create_video_stream(format_context, codec_id, width, height, fps, &codec_context);
    if (!video_stream)
        return -1;

    // 如果需要,打开输出文件
    if (!(format_context->oformat->flags & AVFMT_NOFILE)) {
        if (avio_open(&format_context->pb, filename, AVIO_FLAG_WRITE) < 0) {
            std::cerr << "无法打开输出文件" << std::endl;
            return -1;
        }
    }

    // 写入文件头部信息
    avformat_write_header(format_context, nullptr);

    // 分配YUV420P格式的AVFrame
    AVFrame* frame = av_frame_alloc();
    frame->format = AV_PIX_FMT_YUV420P; // 修改为YUV420P格式
    frame->width = width;
    frame->height = height;
    av_image_alloc(frame->data, frame->linesize, width, height, AV_PIX_FMT_YUV420P, 16); // 最后一个参数,如果视频出现了条纹状可进行修改(1,2,4,8,16,32,64),目前16以下经过测试都可以。

    // CUDA内存分配
    unsigned char* d_gray_img, * d_y_plane, * d_u_plane, * d_v_plane;
    size_t gray_img_size = width * height * sizeof(unsigned char);
    size_t uv_plane_size = (width / 2) * (height / 2) * sizeof(unsigned char);
    cudaMalloc(&d_gray_img, gray_img_size);
    cudaMalloc(&d_y_plane, gray_img_size);
    cudaMalloc(&d_u_plane, uv_plane_size);
    cudaMalloc(&d_v_plane, uv_plane_size);

    // 启动帧采集线程
    std::thread capture_thread(capture_frames, std::ref(camera), width, height, fps, num_frames);

    dim3 block_size(16, 16);
    dim3 grid_size((width + block_size.x - 1) / block_size.x, (height + block_size.y - 1) / block_size.y);

    // 主循环:从队列中获取帧并写入视频流
    int frame_index = 0;
    while (capturing || !frame_queue.empty()) {
        std::unique_lock<std::mutex> lock(frame_mutex);
        frame_cond.wait(lock, [] { return !frame_queue.empty() || !capturing; });

        if (!frame_queue.empty()) {
            cv::Mat gray_img = frame_queue.front();
            frame_queue.pop();
            lock.unlock();

            // 将灰度图像数据复制到CUDA内存
            cudaMemcpy(d_gray_img, gray_img.ptr<unsigned char>(), gray_img_size, cudaMemcpyHostToDevice);

            // 调用CUDA kernel进行格式转换
            gray_to_yuv420p_kernel <<<grid_size, block_size>>> (d_gray_img, d_y_plane, d_u_plane, d_v_plane, width, height);
            cudaDeviceSynchronize();

            // 将CUDA结果复制到AVFrame
            cudaMemcpy(frame->data[0], d_y_plane, gray_img_size, cudaMemcpyDeviceToHost);
            cudaMemcpy(frame->data[1], d_u_plane, uv_plane_size, cudaMemcpyDeviceToHost);
            cudaMemcpy(frame->data[2], d_v_plane, uv_plane_size, cudaMemcpyDeviceToHost);

            // 写入帧到视频流
            write_frame(format_context, video_stream, codec_context, frame, frame_index++);
        }
    }

    // 写入文件尾部信息
    av_write_trailer(format_context);

    if (!(format_context->oformat->flags & AVFMT_NOFILE)) {
        avio_close(format_context->pb);
    }

    // 释放资源
    avcodec_free_context(&codec_context);
    avformat_free_context(format_context);
    av_frame_free(&frame);

    // 释放CUDA内存
    cudaFree(d_gray_img);
    cudaFree(d_y_plane);
    cudaFree(d_u_plane);
    cudaFree(d_v_plane);

    capturing = false;
    frame_cond.notify_one();
    capture_thread.join();

    // 记录程序结束时间并计算运行时长
    auto program_end_time = std::chrono::steady_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::seconds>(program_end_time - program_start_time).count();
    std::cout << "程序运行时长: " << duration << "秒" << std::endl;

    return 0;
}

  • 8
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值