Qt+FFmpeg播放RTSP H264视频流(2)- FFmpeg自定义类封装

FFmpeg操作类封装

在上一篇博客Qt+FFmpeg播放RTSP H264视频流(1)- 在Qt项目加入FFmpeg库中完成了FFmpeg库的加入qt项目的过程,本篇完成FFmepg库的封装,将播放rtsp视频流用到的相关FFmpeg接口进行简单封装得到我们自己的MyFFmpeg类。

FFmpeg播放RTSP流程分析

FFmpeg播放RTSP流程如下图所示:
FFmpeg播放RTSP流程

MyFFmpegSetUrl(QString playUrl) 设置RTSP播放路径

MyFFmpegSetUrl接口用来设置待播放视频路径;

void MyFFmpeg::MyFFmpegSetUrl(QString rtspUrl)
{
    m_rtspUrl = rtspUrl;
}

MyFFmpegInit() 初始化

MyFFmpegInit接口主要执行一系列FFmepg初始化、资源分配、视频流打开等工作

int MyFFmpeg::MyFFmpegInit()
{
    int i;
    int ret = -1;

    // 获取视频播放URL
    QByteArray byteRtspUrl =m_rtspUrl.toLocal8Bit();
    char *pRtspUrl = byteRtspUrl.data();

    // 初始化所有组件,调用该函数后,才能使用复用器和编解码器
    av_register_all();

    // 初始化网络库
    avformat_network_init();

    // 分配AVFormatContext,它是FFMPEG解封装(flv,mp4,rmvb,avi)功能的结构体,
    // 具体可参考https://blog.csdn.net/leixiaohua1020/article/details/14214705
    m_AVFormatContext = avformat_alloc_context();

    // 设置参数
    AVDictionary *options = NULL;
    // 设置传输协议为TCP协议
    av_dict_set(&options, "rtsp_transport", "tcp", 0);

    // 设置TCP连接最大延时时间
    av_dict_set(&options, "max_delay", "100", 0);

    // 设置“buffer_size”缓存容量
    av_dict_set(&options, "buffer_size", "1024000", 0);

    // 设置avformat_open_input超时时间为3秒
    av_dict_set(&options, "stimeout", "3000000", 0);

    // 打开网络流或文件流
    ret = avformat_open_input(&m_AVFormatContext, pRtspUrl, NULL, &options);
    if (ret != 0)
    {
        qDebug("Couldn't open input stream, ret=%d\n", ret);
        return -1;
    }

    // 读取流数据包并获取流的相关信息
    if (avformat_find_stream_info(m_AVFormatContext, NULL) < 0)
    {
        qDebug("Couldn't find stream information.\n");
        return -1;
    }

    // 确定流格式是否为视频
    for (i = 0; i < m_AVFormatContext->nb_streams; i++)
    {
        if (m_AVFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            m_videoIndex = i;
            break;
        }
    }

    if (m_videoIndex == -1)
    {
        qDebug("Didn't find a video stream.\n");
        return -1;
    }

    m_AVCodecContext = m_AVFormatContext->streams[m_videoIndex]->codec;

    // 根据编码器的ID号查找对应的解码器
    m_AVCodec = avcodec_find_decoder(m_AVCodecContext->codec_id);
    if (NULL == m_AVCodec)
    {
        qDebug("avcodec_find_decoder AV_CODEC_ID_H264 fail!\n");
        return -1;
    }

    // 配置编码器上下文的参数
    m_AVCodecContext->bit_rate = 0;         //码率
    m_AVCodecContext->time_base.den = 25;   // 下面2行设置帧率,每秒/25帧
    m_AVCodecContext->time_base.num = 1;
    m_AVCodecContext->frame_number = 1;     //每包一个视频帧

    // Initialize the AVCodecContext to use the given AVCodec
    if (avcodec_open2(m_AVCodecContext, m_AVCodec, NULL) < 0)
    {
        qDebug("avcodec_open2 fail");
        return -1;
    }

    // alloc AVFrame
    m_AVFrame = av_frame_alloc();
    m_AVFrameRGB = av_frame_alloc();

    // 图像色彩空间转换、分辨率缩放、前后图像滤波处理
    m_SwsContext = sws_getContext(m_AVCodecContext->width, m_AVCodecContext->height,
            m_AVCodecContext->pix_fmt, m_AVCodecContext->width, m_AVCodecContext->height,
            AV_PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL);

    int bytes = avpicture_get_size(AV_PIX_FMT_RGB32, m_AVCodecContext->width, m_AVCodecContext->height);
    m_OutBuffer = (uint8_t *)av_malloc(bytes * sizeof(uint8_t));

    // 将分配的内存空间给m_AVFrameRGB使用
    avpicture_fill((AVPicture *)m_AVFrameRGB, m_OutBuffer, AV_PIX_FMT_RGB32, m_AVCodecContext->width, m_AVCodecContext->height);

    // 为AVPacket分别内存空间
    int packSize = m_AVCodecContext->width * m_AVCodecContext->height;
    m_AVPacket = (AVPacket *)malloc(sizeof(AVPacket));
    av_new_packet(m_AVPacket, packSize);

    qDebug("============== MyFFmpegInit ok! ====================== ");

    return 0;
}

MyFFmpegDestroy() 逆初始化

对MyFFmpegInit接口中申请的资源进行释放操作

void MyFFmpeg::MyFFmpegDestroy()
{
    av_free(m_OutBuffer);
    av_free(m_AVFrameRGB);
    av_frame_free(&m_AVFrame);
    av_frame_free(&m_AVFrameRGB);
    sws_freeContext(m_SwsContext);
    av_free_packet(m_AVPacket);
    free(m_AVPacket);
    avcodec_close(m_AVCodecContext);
    avformat_close_input(&m_AVFormatContext);
    avformat_free_context(m_AVFormatContext);
    avformat_network_deinit();
}

MyFFmpegReadFrame() 读取视频帧

MyFFmpegReadFrame没调用一次,尝试读取一帧视频帧数据,读取成功后对视频帧进行解码、缩放、格式转等操作转换成QImage,再通过MyFFmpegSigGetOneFrame()信号将QImage发送出去;

int MyFFmpeg::MyFFmpepReadFrame()
{
    int ret = -1;
    int getPicture = 0;

    // 获取下一帧数据
    ret = av_read_frame(m_AVFormatContext, m_AVPacket);
    if (ret < 0)
    {
        qDebug("av_read_frame fail!\n");
        return -1;
    }

    if (m_AVPacket->stream_index != m_videoIndex)
    {
        av_free_packet(m_AVPacket);
        return 0;
    }

    //  解码m_AVPacket,Decode the video frame of size avpkt->size from avpkt->data into picture
    ret = avcodec_decode_video2(m_AVCodecContext, m_AVFrame, &getPicture, m_AVPacket);
    if (ret < 0)
    {
        qDebug("avcodec_decode_video2 fail!\n");
        av_free_packet(m_AVPacket);
        return 0;
    }

    // got_picture_ptr Zero if no frame could be decompressed, otherwise, it is nonzero.
    // 判断是否已有视频帧被解码了
    if (getPicture)
    {
        // 对解码视频帧进行缩放、格式转换等操作
        sws_scale(m_SwsContext, (uint8_t const * const *)m_AVFrame->data,
                 m_AVFrame->linesize, 0, m_AVCodecContext->height,
                 m_AVFrameRGB->data, m_AVFrameRGB->linesize);

        // 转换到QImage
        QImage tmmImage((uchar *)m_OutBuffer, m_AVCodecContext->width, m_AVCodecContext->height, QImage::Format_RGB32);
        QImage image = tmmImage.copy();

        // 发送QImage
        emit MyFFmpegSigGetOneFrame(image);
    }

    // 释放资源
    av_free_packet(m_AVPacket);

    return 0;
}

MyFFmpegSigGetOneFrame(QImage img) 发送单帧数据信号

发送QImage数据的信号函数

MyFFmpeg类测试

在FFmpegLibTestFrm类中增加一个测试接口MyFFmpegTest

void FFmpegLibTestFrm::MyFFmpegTest()
{
    MyFFmpeg *ffmpeg = new MyFFmpeg();

    QString rtspUrl = "rtsp://192.168.88.168:554/0";
    ffmpeg->MyFFmpegSetUrl(rtspUrl);
    if (ffmpeg->MyFFmpegInit() == 0) {
        int i;
        for (i = 0; i < 5; i++) {
            ffmpeg->MyFFmpepReadFrame();
        }
    } else {
        qDebug("MyFFmpegInit fail!\n");
    }

    delete ffmpeg;
}

在FFmpegLibTestFrm构造函数中调用一次MyFFmpegTest接口,控制台输入结果如下
MyFFmpeg测试

源码下载

https://download.csdn.net/download/eastcnme/11289361

参考资源

AVPacket详解: https://blog.csdn.net/with_dream/article/details/84928280
AVCodecContext参数:https://blog.csdn.net/chance_yin/article/details/16335625
avcodec_open2(): https://blog.csdn.net/leixiaohua1020/article/details/44117891

  • 1
    点赞
  • 65
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Qt中使用FFmpeg播放RTSP H264视频流的方法如下[^1][^2]: 1. 首先,确保你已经安装了QtFFmpeg,并且已经将FFmpeg的库文件添加到Qt项目中。 2. 创建一个Qt项目,并在项目文件中添加FFmpeg的头文件和库文件的路径。 3. 在Qt的代码中,使用FFmpeg的API来实现视频流播放。以下是一个简单的示例代码: ```cpp #include <QCoreApplication> #include <QDebug> #include <QThread> extern "C" { #include <libavformat/avformat.h> #include <libavcodec/avcodec.h> #include <libswscale/swscale.h> } int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); // 初始化FFmpeg av_register_all(); // 创建AVFormatContext对象 AVFormatContext *formatContext = avformat_alloc_context(); // 打开视频流 if (avformat_open_input(&formatContext, "rtsp://your_rtsp_url", nullptr, nullptr) != 0) { qDebug() << "无法打开视频流"; return -1; } // 查找视频流信息 if (avformat_find_stream_info(formatContext, nullptr) < 0) { qDebug() << "无法获取视频流信息"; return -1; } // 查找视频流索引 int videoStreamIndex = -1; for (int i = 0; i < formatContext->nb_streams; i++) { if (formatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { videoStreamIndex = i; break; } } // 如果找不到视频流索引,退出程序 if (videoStreamIndex == -1) { qDebug() << "找不到视频流"; return -1; } // 获取视频解码器参数 AVCodecParameters *codecParameters = formatContext->streams[videoStreamIndex]->codecpar; // 查找视频解码器 AVCodec *codec = avcodec_find_decoder(codecParameters->codec_id); if (codec == nullptr) { qDebug() << "找不到视频解码器"; return -1; } // 创建解码器上下文 AVCodecContext *codecContext = avcodec_alloc_context3(codec); if (avcodec_parameters_to_context(codecContext, codecParameters) < 0) { qDebug() << "无法创建解码器上下文"; return -1; } // 打开解码器 if (avcodec_open2(codecContext, codec, nullptr) < 0) { qDebug() << "无法打开解码器"; return -1; } // 创建帧对象 AVFrame *frame = av_frame_alloc(); // 创建解码后的帧对象 AVFrame *decodedFrame = av_frame_alloc(); // 创建解码后的帧的缓冲区 uint8_t *buffer = nullptr; int bufferSize = av_image_get_buffer_size(AV_PIX_FMT_RGB24, codecContext->width, codecContext->height, 1); buffer = (uint8_t *)av_malloc(bufferSize * sizeof(uint8_t)); av_image_fill_arrays(decodedFrame->data, decodedFrame->linesize, buffer, AV_PIX_FMT_RGB24, codecContext->width, codecContext->height, 1); // 创建视频转换上下文 SwsContext *swsContext = sws_getContext(codecContext->width, codecContext->height, codecContext->pix_fmt, codecContext->width, codecContext->height, AV_PIX_FMT_RGB24, SWS_BILINEAR, nullptr, nullptr, nullptr); // 读取视频帧 AVPacket packet; while (av_read_frame(formatContext, &packet) >= 0) { if (packet.stream_index == videoStreamIndex) { // 解码视频帧 avcodec_send_packet(codecContext, &packet); avcodec_receive_frame(codecContext, frame); // 转换视频帧格式 sws_scale(swsContext, frame->data, frame->linesize, 0, codecContext->height, decodedFrame->data, decodedFrame->linesize); // 在这里可以将解码后的帧显示到界面上 // 延时一段时间,模拟视频播放 QThread::msleep(40); } av_packet_unref(&packet); } // 释放资源 av_frame_free(&frame); av_frame_free(&decodedFrame); avcodec_free_context(&codecContext); avformat_close_input(&formatContext); avformat_free_context(formatContext); return a.exec(); } ``` 请注意,上述代码只是一个简单的示例,实际的视频播放功能可能需要更多的处理和错误处理。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值