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);
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用eNSP配置GRE VPN实验

实验拓扑 实验需求 1.按照图示配置IP地址 2.在R1和R3上配置默认路由使公网区域互通 3.在R1和R3上配置GRE VPN&#xff0c;使两端私网能够互相访问&#xff0c;Tunne1口IP地址如图 4.在R1和R3上配置RIPv2来传递两端私网路由 实验步骤 GRE VPN配置方法&#xff1a; 发送端&#x…

机器学习-线性回归(对于f(x;w)=w^Tx+b理解)

一、&#x1d453;(&#x1d499;;&#x1d498;) &#x1d498;T&#x1d499;的推导 学习线性回归&#xff0c;我们那先要对于线性回归的表达公示&#xff0c;有所认识。 我们先假设空间是一组参数化的线性函数&#xff1a; 其中权重向量&#x1d498; ∈ R&#x1d437; …

Swing使用MVC模型架构

什么是MVC模式? MVC是一组英文的缩写,其全名是Model-View-Controller,也就是“模型-视图-控制器”这三个部分组成。这三个部分任意一个部分发生变化都会引起另外两个发生变化。三者之间的关系示意图如下所示: MVC分为三个部分,所以在MVC模型中将按照此三部分分成三…

Windows 环境下 Docker Desktop + Kubernetes 部署项目指南

Windows 环境下 Docker Desktop Kubernetes 部署项目指南 一、环境准备二、安装与配置 Kubernetes安装 windows 版的 docker启动 kubernetes安装 windows 版的 kubectl 工具下载 k8s-for-docker-desktop启动 Kubernetes Dashboard 二、在 Kubernetes 上部署项目创建一个 demo …

redis实现lamp架构缓存

redis服务器环境下mysql实现lamp架构缓存 ip角色环境192.168.242.49缓存服务器Redis2.2.7192.168.242.50mysql服务器mysql192.168.242.51web端php ***默认已安装好redis&#xff0c;mysql 三台服务器时间同步&#xff08;非常重要&#xff09; # 下载ntpdate yum -y install…

【Excel】【VBA】Reaction超限点筛选与散点图可视化

【Excel】【VBA】Reaction超限点筛选与散点图可视化 功能概述 这段代码实现了以下功能&#xff1a; 从SAFE输出的结果worksheet通过datalink获取更新数据从指定工作表中读取数据检测超过阈值的数据点生成结果表格并添加格式化创建可视化散点图显示执行时间 流程图 #mermaid-…

Java导出通过Word模板导出docx文件并通过QQ邮箱发送

一、创建Word模板 {{company}}{{Date}}服务器运行情况报告一、服务器&#xff1a;总告警次数&#xff1a;{{ServerTotal}} 服务器IP:{{IPA}}&#xff0c;总共告警次数:{{ServerATotal}} 服务器IP:{{IPB}}&#xff0c;总共告警次数:{{ServerBTotal}} 服务器IP:{{IPC}}&#x…

智能化加速标准和协议的更新并推动验证IP(VIP)在芯片设计中的更广泛应用

作者&#xff1a;Karthik Gopal, SmartDV Technologies亚洲区总经理 智权半导体科技&#xff08;厦门&#xff09;有限公司总经理 随着AI技术向边缘和端侧设备广泛渗透&#xff0c;芯片设计师不仅需要考虑在其设计中引入加速器&#xff0c;也在考虑采用速度更快和带宽更高的总…

RabbitMQ5-死信队列

目录 死信的概念 死信的来源 死信实战 死信之TTl 死信之最大长度 死信之消息被拒 死信的概念 死信&#xff0c;顾名思义就是无法被消费的消息&#xff0c;一般来说&#xff0c;producer 将消息投递到 broker 或直接到queue 里了&#xff0c;consumer 从 queue 取出消息进…

git常用命令学习

目录 文章目录 目录第一章 git简介1.Git 与SVN2.Git 工作区、暂存区和版本库 第二章 git常用命令学习1.ssh设置2.设置用户信息3.常用命令设置1.初始化本地仓库init2.克隆clone3.查看状态 git status4.添加add命令5.添加评论6.分支操作1.创建分支2.查看分支3.切换分支4.删除分支…

私有包上传maven私有仓库nexus-2.9.2

一、上传 二、获取相应文件 三、最后修改自己的pom文件

汽车定速巡航

配备定速巡航功能的车型&#xff0c;一般在方向盘附近设有4~6个按键&#xff08;可能共用键位&#xff09;。 要设置定速巡航&#xff0c;不仅需要方向盘上的按键&#xff0c;还要油门配合。 设置的一般流程&#xff1a; 开关&#xff1a;类似步枪上的“保险”&#xff0c;按…

安宝特方案 | AR在供应链管理中的应用:提升效率与透明度

随着全球化的不断深入和市场需求的快速变化&#xff0c;企业对供应链管理的要求也日益提高。如何在复杂的供应链环境中提升效率、降低成本&#xff0c;并确保信息的透明度&#xff0c;成为了各大行业亟待解决的问题。而增强现实&#xff08;AR&#xff09;技术&#xff0c;特别…

JavaScript(8)-函数

一.什么是函数&#xff1a;执行特定任务的代码块 讲js中需要的公共部分抽取并封装&#xff0c;谁用谁调用&#xff0c;代码复用。 先声明&#xff0c;后调用 使用function函数名需要调用的内容 使用&#xff1a;函数名&#xff08;&#xff09; 二.使用方式 声明&a…

HTML<label>标签

例子 三个带标签的单选按钮&#xff1a; <form action"/action_page.php"> <input type"radio" id"html" name"fav_language" value"HTML"> <label for"html">HTML</label><br&…

4.flask-SQLAlchemy,表Model定义、增删查改操作

介绍 SQLAlchemy是对数据库的一个抽象 开发者不用直接与SQL语句打交道 Python对象来操作数据库 SQLAlchemy是一个关系型数据库 安装 flask中SQLAlchemy的配置 from flask import Flask from demo.user_oper import userdef create_app():app Flask(__name__)# 使用sessi…

【C++初阶】第11课—vector

文章目录 1. 认识vector2. vector的遍历3. vector的构造4. vector常用的接口5. vector的容量6. vector的元素访问7. vector的修改8. vector<vector\<int\>>的使用9. vector的使用10. 模拟实现vector11. 迭代器失效11.1 insert插入数据内部迭代器失效11.2 insert插入…

GPT 结束语设计 以nanogpt为例

GPT 结束语设计 以nanogpt为例 目录 GPT 结束语设计 以nanogpt为例 1、简述 2、分词设计 3、结束语断点 1、简述 在手搓gpt的时候&#xff0c;可能会遇到一些性能问题&#xff0c;即关于是否需要全部输出或者怎么节约资源。 在输出语句被max_new_tokens 限制&#xff0c…

PTMD2.0-疾病相关的翻译后修饰数据库

翻译后修饰&#xff08;PTMs&#xff0c;post-translational modifications&#xff09;通过调节蛋白质功能参与了几乎所有的生物学过程&#xff0c;而 PTMs 的异常状态常常与人类疾病相关。在此&#xff0c;PTMD 2.0展示与疾病相关的 PTMs 综合数据库&#xff0c;其中包含 93 …

ArcGIS10.2 许可License点击始终启动无响应的解决办法及正常启动的前提

1、问题描述 在ArcGIS License Administrator中&#xff0c;手动点击“启动”无响应&#xff1b;且在计算机管理-服务中&#xff0c;无ArcGIS License 或者License的启动、停止、禁止等均为灰色&#xff0c;无法操作。 2、解决方法 ①通过cmd对service.txt进行手动服务的启动…