Qt调用FFmpeg库实时播放UDP组播视频流

基于以下参考链接,通过改进实现实时播放UDP组播视频流

https://blog.csdn.net/u012532263/article/details/102736700

源码在windows(qt-opensource-windows-x86-5.12.9.exe)、ubuntu20.04.6(x64)(qt-opensource-linux-x64-5.12.12.run)、以及针对arm64的ubuntu20.04.6(x64)交叉编译环境下编译成功(QT5.12.8, 5.15.13), 可执行程序在windows,ubuntu(x64)、arm64上均可运行。

工程代码见:

https://download.csdn.net/download/daqinzl/90315016

主要代码
videoplayer.cpp

#include "videoplayer.h"
#include <QDebug>
extern "C"
{
    #include "libavcodec/avcodec.h"
    #include "libavformat/avformat.h"
    #include "libavutil/pixfmt.h"
    #include "libswscale/swscale.h"
}

#include <stdio.h>
#include<iostream>
using namespace std;
VideoPlayer::VideoPlayer()
{

}

VideoPlayer::~VideoPlayer()
{

}

void VideoPlayer::startPlay()
{
    ///调用 QThread 的start函数 将会自动执行下面的run函数 run函数是一个新的线程
    this->start();

}

void VideoPlayer::run()
{
    /*
    AVFormatContext *pFormatCtx;
    AVCodecContext *pCodecCtx;
    AVCodec *pCodec;
    AVFrame *pFrame;
    AVFrame *pFrameRGB;
    AVPacket *packet;
    uint8_t *out_buffer;

    static struct SwsContext *img_convert_ctx;

    int videoStream, i, numBytes;
    int ret, got_picture;

    avformat_network_init();
    av_register_all();


    //Allocate an AVFormatContext.
    pFormatCtx = avformat_alloc_context();

    // ffmpeg取rtsp流时av_read_frame阻塞的解决办法 设置参数优化
    AVDictionary* avdic = NULL;
    //rtsp
    //av_dict_set(&avdic, "buffer_size", "102400", 0); //设置缓存大小,1080p可将值调大
    //av_dict_set(&avdic, "rtsp_transport", "udp", 0); //以udp方式打开,如果以tcp方式打开将udp替换为tcp

    //rtmp
    //av_dict_set(&avdic, "buffer_size", "8192000", 0); //设置缓存大小,1080p可将值调大
    //av_dict_set(&avdic, "rtsp_transport", "tcp", 0); //以udp方式打开,如果以tcp方式打开将udp替换为tcp

    //udp
    av_dict_set(&avdic, "buffer_size", "8192000", 0); //设置缓存大小,1080p可将值调大
    av_dict_set(&avdic, "rtsp_transport", "udp", 0); //以udp方式打开,如果以tcp方式打开将udp替换为tcp
    //av_dict_set(&avdic, "fflags", "nobuffer", 0); // 设置实时选项
    //av_dict_set(&avdic, "flags", "low_delay", 0); // 设置低延迟选项
    av_dict_set(&avdic, "max_interleave_delta", "40000", 0);


    //av_dict_set(&avdic, "stimeout", "2000000", 0);   //设置超时断开连接时间,单位微秒
    //av_dict_set(&avdic, "max_delay", "500000", 0);   //设置最大时延

    ///rtsp地址,可根据实际情况修改
    //char url[]="rtsp://admin:admin@192.168.1.18:554/h264/ch1/main/av_stream";
    //char url[]="rtsp://192.168.17.112/test2.264";
    //char url[]="rtsp://admin:admin@192.168.43.1/stream/main";
    //char url[]="rtmp://mobliestream.c3tv.com:554/live/goodtv.sdp";

    char url[]="udp://224.1.1.1:5001";

    //char url[]="rtmp://192.168.1.100:1935/live/desktop";

    if (avformat_open_input(&pFormatCtx, url, NULL, &avdic) != 0) {
        qDebug("can't open the file. \n");
        return;
    }

    if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
        qDebug("Could't find stream infomation.\n");
        return;
    }

    videoStream = -1;

    ///循环查找视频中包含的流信息,直到找到视频类型的流
    ///便将其记录下来 保存到videoStream变量中
    ///这里我们现在只处理视频流  音频流先不管他
    for (i = 0; i < pFormatCtx->nb_streams; i++) {
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoStream = i;
        }
    }

    ///如果videoStream为-1 说明没有找到视频流
    if (videoStream == -1) {
        qDebug("Didn't find a video stream.\n");
        return;
    }

    ///查找解码器
    pCodecCtx = pFormatCtx->streams[videoStream]->codec;
    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
    ///2017.8.9---lizhen
    //pCodecCtx->bit_rate = 0;   //初始化为0
    //pCodecCtx->time_base.num = 1;  //下面两行:一秒钟25帧
    //pCodecCtx->time_base.den = 10;
    //pCodecCtx->frame_number = 1;  //每包一个视频帧

    if (pCodec == NULL) {
        qDebug("Codec not found.\n");
        return;
    }

    ///打开解码器
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
        qDebug("Could not open codec.\n");
        return;
    }

    pFrame = av_frame_alloc();
    pFrameRGB = av_frame_alloc();

    ///这里我们改成了 将解码后的YUV数据转换成RGB32
    img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
            pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
            AV_PIX_FMT_RGB32, SWS_FAST_BILINEAR, NULL, NULL, NULL);

    numBytes = avpicture_get_size(AV_PIX_FMT_RGB32, pCodecCtx->width,pCodecCtx->height);

    out_buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
    avpicture_fill((AVPicture *) pFrameRGB, out_buffer, AV_PIX_FMT_RGB32,
            pCodecCtx->width, pCodecCtx->height);

    int y_size = pCodecCtx->width * pCodecCtx->height;

    packet = (AVPacket *) malloc(sizeof(AVPacket)); //分配一个packet
    av_new_packet(packet, y_size); //分配packet的数据
    //*/

    //bool CanRun = true;

    //ffmpeg 初始化
    // 初始化注册ffmpeg相关的编码器
    av_register_all();
    avcodec_register_all();
    avformat_network_init();

    //qDebug()<<"1 FFmpeg version info: {  av_version_info() }";
    qDebug()<<"2 FFmpeg version info: { " << av_version_info() << "}";
    qDebug()<<"3 FFmpeg version info:";
    qDebug()<< av_version_info() ;

    char url[]="udp://224.1.1.1:5001";

    // ffmpeg 转码
    // 分配音视频格式上下文
    AVFormatContext *pFormatCtx = avformat_alloc_context();

    AVDictionary* avdic = NULL;
    av_dict_set(&avdic, "buffer_size", "8192000", 0);
    av_dict_set(&avdic, "max_interleave_delta", "40000", 0);

    av_dict_set(&avdic, "analyzeduration", "100000000", 0);
    av_dict_set(&avdic, "probesize", "100000000", 0);

    int ret;
    //打开流
    ret = avformat_open_input(&pFormatCtx, url, NULL, &avdic);
    if (ret != 0){ qDebug("can't open the url. \n"); return; }

    // 读取媒体流信息
    ret = avformat_find_stream_info(pFormatCtx, NULL);
    if (ret != 0) { qDebug("Could't find stream infomation.\n"); return; }

    // 这里只是为了打印些视频参数
    AVDictionaryEntry* tag = NULL;
    while ((tag = av_dict_get(pFormatCtx->metadata, "", tag, AV_DICT_IGNORE_SUFFIX)) != NULL)
    {
        char * key = tag->key;
        char * value = tag->value;
        qDebug()<< *key  << *value << "\n";
    }

    // 从格式化上下文获取流索引
    AVStream* pStream = NULL;
    //AVStream* aStream = NULL;
    int videoStream = -1;
    for (int i = 0; i < pFormatCtx->nb_streams; i++)
    {
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            pStream = pFormatCtx->streams[i];
            videoStream = i;
        }
        //else if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
        //{
        //    aStream = pFormatCtx->streams[i];
        //}
    }
    if (pStream == NULL) { qDebug("Didn't find a video stream.\n"); return; }

    // 获取流的编码器上下文
    AVCodecContext codecContext = *pStream->codec;

    qDebug() << "codec name: {" << avcodec_get_name(codecContext.codec_id) << "}\n";
    // 获取图像的宽、高及像素格式
    int width = codecContext.width;
    int height = codecContext.height;
    AVPixelFormat sourcePixFmt = codecContext.pix_fmt;

    // 得到编码器ID
    AVCodecID codecId = codecContext.codec_id;
    // 目标像素格式
    AVPixelFormat destinationPixFmt = AV_PIX_FMT_RGB32;

    // 某些264格式codecContext.pix_fmt获取到的格式是AV_PIX_FMT_NONE 统一都认为是YUV420P
    if (sourcePixFmt == AV_PIX_FMT_NONE && codecId == AV_CODEC_ID_MPEG2TS)
    {
        sourcePixFmt = AV_PIX_FMT_YUV420P;
    }

    static struct SwsContext *img_convert_ctx;

    // 得到SwsContext对象:用于图像的缩放和转换操作
    img_convert_ctx = sws_getContext(width, height, sourcePixFmt,
        width, height, destinationPixFmt,
        SWS_FAST_BILINEAR, NULL, NULL, NULL);
    if (img_convert_ctx == NULL) { qDebug() << "Could not initialize the conversion context.\n" ; return; }

    //分配一个默认的帧对象:AVFrame
    //AVFrame* pConvertedFrame = av_frame_alloc();
    // 目标媒体格式需要的字节长度
    //numBytes = avpicture_get_size(AV_PIX_FMT_RGB32, pCodecCtx->width,pCodecCtx->height);
    int numBytes = avpicture_get_size(destinationPixFmt, width, height);
    // 分配目标媒体格式内存使用
    //out_buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
    uint8_t * out_buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
    //var dstData = new byte_ptrArray4();
    //var dstLinesize = new int_array4();
    AVFrame *pFrameRGB = av_frame_alloc();
    // 设置图像填充参数
    //av_image_fill_arrays(pFrameRGB->data, pFrameRGB->linesize, convertedFrameBufferPtr, destinationPixFmt, width, height, 1);
    avpicture_fill((AVPicture *) pFrameRGB, out_buffer, destinationPixFmt,width, height);

    // ffmpeg 解码
    // 根据编码器ID获取对应的解码器
    AVCodec* pCodec = avcodec_find_decoder(codecId);
    if (pCodec == NULL) { qDebug() << "Unsupported codec.\n"; }

    AVCodecContext* pCodecCtx = &codecContext;

    if ((pCodec->capabilities & AV_CODEC_CAP_TRUNCATED) == AV_CODEC_CAP_TRUNCATED)
        pCodecCtx->flags |= AV_CODEC_FLAG_TRUNCATED;

    // 通过解码器打开解码器上下文:AVCodecContext pCodecContext
    ret = avcodec_open2(pCodecCtx, pCodec, NULL);
    if (ret < 0) { qDebug() << ret << "\n"; return; }

    // 分配解码帧对象:AVFrame pDecodedFrame
    AVFrame* pFrame = av_frame_alloc();

    // 初始化媒体数据包
    AVPacket* packet = new AVPacket();
    AVPacket** pPacket = &packet;
    av_init_packet(packet);

    while (1)
    {
        /*
        if (av_read_frame(pFormatCtx, packet) < 0)
        {
            qDebug() << "av_read_frame < 0";
            break; //这里认为视频读取完了
        }

        if (packet->stream_index == videoStream) {
            ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture,packet);

            if (ret < 0) {
                qDebug("decode error.\n");
                return;
            }

            if (got_picture) {
                sws_scale(img_convert_ctx,
                        (uint8_t const * const *) pFrame->data,
                        pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data,
                        pFrameRGB->linesize);

                //把这个RGB数据 用QImage加载
                QImage tmpImg((uchar *)out_buffer,pCodecCtx->width,pCodecCtx->height,QImage::Format_RGB32);
                QImage image = tmpImg.copy(); //把图像复制一份 传递给界面显示
                emit sig_GetOneFrame(image);  //发送信号
                //emit sig_GetOneFrame(tmpImg);


                //提取出图像中的R数据
                //for(int i=0;i<pCodecCtx->width;i++)
                //{
                //    for(int j=0;j<pCodecCtx->height;j++)
                //    {
                //        QRgb rgb=image.pixel(i,j);
                //        int r=qRed(rgb);
                //        image.setPixel(i,j,qRgb(r,0,0));
                //    }
                //}
                //emit sig_GetRFrame(image);


            }else qDebug() << "got_picture < 0";
        }else qDebug() << "packet->stream_index not video stream";
        av_free_packet(packet); //释放资源,否则内存会一直上升
        //msleep(0.02); //停一停  不然放的太快了
        //*/

        //*
        try
        {
            do
            {
                // 读取一帧未解码数据
                ret = av_read_frame(pFormatCtx, packet);
               // Console.WriteLine(pPacket->dts);
                if (ret == AVERROR_EOF) break;
                if (ret < 0) { qDebug() << "got error "; return; }

                if (packet->stream_index != videoStream) continue;

                // 解码
                ret = avcodec_send_packet(pCodecCtx, packet);
                if (ret < 0) { qDebug() << "got error 2 "; return; }
                // 解码输出解码数据
                ret = avcodec_receive_frame(pCodecCtx, pFrame);
            } while (ret == AVERROR(EAGAIN) && 1);
            if (ret == AVERROR_EOF) break;
            if (ret < 0) { qDebug() << "got error 3 "; return; }

            if (packet->stream_index != videoStream) continue;

            //Console.WriteLine($@"frame: {frameNumber}");
            // YUV->RGB
            sws_scale(img_convert_ctx, pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize);

            //把这个RGB数据 用QImage加载
            QImage tmpImg((uchar *)out_buffer,pCodecCtx->width,pCodecCtx->height,QImage::Format_RGB32);
            QImage image = tmpImg.copy(); //把图像复制一份 传递给界面显示
            emit sig_GetOneFrame(image);  //发送信号
            //emit sig_GetOneFrame(tmpImg);

        }  catch (exception e) {

        }
        {
            av_packet_unref(packet);//释放数据包对象引用
            av_frame_unref(pFrame);//释放解码帧对象引用
        }
        //*/
    }
    av_free(out_buffer);
    av_free(pFrameRGB);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/68222.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu 20.04 x64下 编译安装ffmpeg

试验的ffmpeg版本 4.1.3 本文使用的config命令 ./configure --prefixhost --enable-shared --disable-static --disable-doc --enable-postproc --enable-gpl --enable-swscale --enable-nonfree --enable-libfdk-aac --enable-decoderh264 --enable-libx265 --enable-libx…

如何在IDEA社区版Service面板中管理springboot项目

1、开启service仪表盘 2、在service仪表盘中&#xff0c;添加启动类配置项&#xff0c;专业版是SpringBoot 、社区版是application。 3、控制台彩色日志输出 右键启动类配置项&#xff0c;添加虚拟机参数 -Dspring.output.ansi.enabledALWAYS

Vue.js组件开发-如何实现带有搜索功能的下拉框

创建 Vue 项目&#xff1a; 如果还没有创建一个 Vue 项目&#xff0c;可以使用 Vue CLI 来创建一个新的项目。 vue create my-project cd my-project安装依赖&#xff1a; 如果需要使用第三方组件库&#xff0c;比如 Element UI 或 Ant Design Vue&#xff0c;可以安装相应的…

网盘资源查找工具---AI功能

01 软件介绍 这是一款融入了ai技术的网盘搜索神器&#xff0c;可以让你更快&#xff0c;更精准的找到自己需要的文件&#xff0c;不管你是找影视&#xff0c;音乐&#xff0c;还是找软件或者学习资料都可以&#xff0c;欢迎前来使用。 02 功能展示 该软件非常简洁&#xff…

【2025年数学建模美赛E题】(农业生态系统)完整解析+模型代码+论文

生态共生与数值模拟&#xff1a;生态系统模型的物种种群动态研究 摘要1Introduction1.1Problem Background1.2Restatement of the Problem1.3Our Work 2 Assumptions and Justifications3 Notations4 模型的建立与求解4.1 农业生态系统模型的建立与求解4.1.1 模型建立4.1.2求解…

【Elasticsearch】index:false

在 Elasticsearch 中&#xff0c;index 参数用于控制是否对某个字段建立索引。当设置 index: false 时&#xff0c;意味着该字段不会被编入倒排索引中&#xff0c;因此不能直接用于搜索查询。然而&#xff0c;这并不意味着该字段完全不可访问或没有其他用途。以下是关于 index:…

FPGA 使用 CLOCK_LOW_FANOUT 约束

使用 CLOCK_LOW_FANOUT 约束 您可以使用 CLOCK_LOW_FANOUT 约束在单个时钟区域中包含时钟缓存负载。在由全局时钟缓存直接驱动的时钟网段 上对 CLOCK_LOW_FANOUT 进行设置&#xff0c;而且全局时钟缓存扇出必须低于 2000 个负载。 注释&#xff1a; 当与其他时钟约束配合…

蓝桥杯3518 三国游戏 | 排序

题目传送门 这题的思路很巧妙&#xff0c;需要算出每个事件给三国带来的净贡献&#xff08;即本国士兵量减其他两国士兵量&#xff09;并对其排序&#xff0c;根据贪心的原理累加贡献量直到累加结果不大于0。最后对三国的胜利的最大事件数排序取最值即可。 n int(input()) a …

【redis初阶】redis客户端

目录 一、基本介绍 二、认识RESP&#xff08;redis自定的应用层协议名称&#xff09; 三、访问github的技巧 四、安装redisplusplus 4.1 安装 hiredis** 4.2 下载 redis-plus-plus 源码 4.3 编译/安装 redis-plus-plus 五、编写运行helloworld 六、redis命令演示 6.1 通用命令的…

LLM - 大模型 ScallingLaws 的设计 100B 预训练方案(PLM) 教程(5)

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/145356022 免责声明&#xff1a;本文来源于个人知识与公开资料&#xff0c;仅用于学术交流&#xff0c;欢迎讨论&#xff0c;不支持转载。 Scalin…

HttpClient学习

目录 一、概述 二、HttpClient依赖介绍 1.导入HttpClient4依赖 2.或者导入HttpClient5依赖 3.二者区别 三、HttpClient发送Get请求和Post请求测试 (一)通过HttpClient发送Get请求 (二)通过HttpClient发送Post请求 一、概述 HttpClient是 Apache 软件基金会提供的一…

FAST-DDS and ROS2 RQT connect

reference: FAST-DDS与ROS2通信_ros2 收fastdds的数据-CSDN博客 software version: repositories: foonathan_memory_vendor: type: git url: https://github.com/eProsima/foonathan_memory_vendor.git version: v1.1.0 fastcdr: …

Kafka后台启动命令

#保存日志 nohup ./kafka-server-start.sh ../config/server.properties > /path/to/logfile.log 2>&1 &#不保存日志 nohup ./kafka-server-start.sh ../config/server.properties >/dev/null 2>&1 & nohup: 是一个Unix/Linux命令&#xff0c;用于…

蓝桥杯算法赛第25场月赛

前言 这些题对于我的难度有点大&#xff0c;大家感兴趣的可以来做一下&#xff0c;看一下&#xff0c;下面给大家展示一下题目 1. 桃花运走向【算法赛】 问题描述 2025 年春节&#xff0c;小明和小红兴致勃勃地去庙会玩耍。庙会上&#xff0c;一个算命先生摆摊算命&#xf…

开源先锋DeepSeek-V3 LLM 大语言模型本地调用,打造自己专属 AI 助手

DeepSeek-V3是一个强大的混合专家 (MoE) 语言模型&#xff0c;总共有 671B 个参数。为了实现高效的推理和经济高效的训练&#xff0c;DeepSeek-V3 采用了多头潜在注意力机制 (MLA) 和 DeepSeekMoE 架构&#xff0c;这些架构在 DeepSeek-V2 中得到了彻底的验证。此外&#xff0c…

喜报丨迪捷软件入选2025年浙江省“重点省专”

根据《浙江省经济和信息化厅 浙江省财政厅关于进一步支持专精特新中小企业高质量发展的通知》&#xff08;浙经信企业〔2024〕232号&#xff09;有关要求&#xff0c;经企业自主申报、地方推荐、材料初审以及专家评审等程序&#xff0c;浙江省经济和信息化厅发布了2025年浙江省…

简识JVM中并发垃圾回收器和多线程并行垃圾回收器的区别

在JVM中&#xff0c;多线程并行垃圾回收器和并发垃圾回收器是两种不同类型的垃圾回收机制&#xff0c;它们的主要区别在于垃圾收集线程与用户线程之间的运行关系&#xff0c;以及这种关系对应用程序性能的影响。以下是对这两种垃圾回收器的详细比较&#xff1a; 一、多线程并行…

Golang Gin系列-8:单元测试与调试技术

在本章中&#xff0c;我们将探讨如何为Gin应用程序编写单元测试&#xff0c;使用有效的调试技术&#xff0c;以及优化性能。这包括设置测试环境、为处理程序和中间件编写测试、使用日志记录、使用调试工具以及分析应用程序以提高性能。 为Gin应用程序编写单元测试 设置测试环境…

通过 NAudio 控制电脑操作系统音量

根据您的需求&#xff0c;以下是通过 NAudio 获取和控制电脑操作系统音量的方法&#xff1a; 一、获取和控制系统音量 &#xff08;一&#xff09;获取系统音量和静音状态 您可以使用 NAudio.CoreAudioApi.MMDeviceEnumerator 来获取系统默认音频设备的音量和静音状态&#…

深度学习 Pytorch 单层神经网络

神经网络是模仿人类大脑结构所构建的算法&#xff0c;在人脑里&#xff0c;我们有轴突连接神经元&#xff0c;在算法中&#xff0c;我们用圆表示神经元&#xff0c;用线表示神经元之间的连接&#xff0c;数据从神经网络的左侧输入&#xff0c;让神经元处理之后&#xff0c;从右…