QT +ffmpeg-4.2.2-win64-shared 拉取 RTMP/http-flv 流播放

news2024/11/14 14:31:39

拉取HTTP-FLV视频流处理逻辑:

1.在子线程中从流媒体服务端拉取视频流、使用ffmpeg进行解码,转成QImage ,发送给主线程。

2.主线程接收QImage后在界面显示。

pro文件:

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++17

# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
    cc_video_thread.cpp \
    main.cpp \
    httpflvmainwindow.cpp

HEADERS += \
    cc_video_thread.h \
    httpflvmainwindow.h

FORMS += \
    httpflvmainwindow.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
INCLUDEPATH += $$PWD/lib/ffmpeg/include
LIBS += -L$$PWD/lib/ffmpeg/lib -lavcodec -lavdevice -lavfilter -lavformat -lavutil -lpostproc -lswresample -lswscale

子线程:

  Cc_Video_thread .h

#ifndef CC_VIDEO_THREAD_H
#define CC_VIDEO_THREAD_H

#include <QThread>
#include <QAtomicInt>
#include <QImage>


#ifdef __cplusplus
extern "C"
{
#endif
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavcodec/avcodec.h>

#include <libavutil/frame.h>
#include <libavutil/mem.h>
#include <libavutil/dict.h>
#include <libavutil/imgutils.h>

#ifdef __cplusplus
}
#endif

class Cc_Video_thread : public QThread
{
    Q_OBJECT
public:
    Cc_Video_thread();
    ~Cc_Video_thread();

    void run() override;

    /**
     * @brief open stream and decode
     * @param addr
     */
    int open(const QString &addr);

    /**
     * @brief exit
     */
    void exit();

    void packer_to_qimage();

    /**
     * @brief custom_interrupt_callback
     * @return
     */
    static int custom_interrupt_callback(void *){
        //        LOG(ERROR) <<"[ERROR]:OUT TIME..."<<std::endl;
        return 0;
    };

signals:
    void sendimage(QImage img);

private:
    QAtomicInt exit_state_ = 1; // thread exit state
    QAtomicInt save_state_ = 1; //
    QAtomicInt open_state_ = -1;

    AVCodec* codec_;
    AVPacket* packet_;
    AVStream * video_st;
    AVFrame* yuv_frame_;
    AVFrame* pFrameRGB;
    AVCodecContext* codecContext;

    AVFormatContext* format_context_;
    AVCodecParameters *codecParam;
    SwsContext* y2r_sws_context_;
    int video_stream_index_ = 0;
    int audio_stream_index_ = 0;
    int video_frame_size = 0 ;
    int audio_frame_size = 0;
    int video_frame_count = 0;
    int audio_frame_count = 0;

    uint8_t *m_OutBuffer;
};

#endif // CC_VIDEO_THREAD_H

Cc_Video_thread.cpp 

#include "cc_video_thread.h"

#include <string>
#include <QPixmap>
#include <QDebug>
Cc_Video_thread::Cc_Video_thread()
{
    QString m_url=改成自己的url;
    start();
    open(m_url);
}

Cc_Video_thread::~Cc_Video_thread()
{
    exit_state_ = 0;

    av_packet_free(&packet_);
    av_frame_free(&pFrameRGB);
    av_frame_free(&yuv_frame_);
    avformat_close_input(&format_context_);
}

int Cc_Video_thread::open(const QString &addr)
{
    if(open_state_ == 1){
        return -1;
    }
    if(addr.isEmpty())
    {
        //qDebug() << "[ERROR] addr is empty... ";
        return -1;
    }
    // init ffmpeg and open stream
    //qDebug() << avcodec_configuration() << endl;
    format_context_ = avformat_alloc_context();
    format_context_->interrupt_callback.callback=custom_interrupt_callback;
    format_context_->interrupt_callback.opaque=this;
    codecContext=avcodec_alloc_context3(nullptr);
    packet_  = av_packet_alloc();
    yuv_frame_=av_frame_alloc();
    pFrameRGB = av_frame_alloc();

    avformat_network_init(); // init net work
    AVDictionary *options = nullptr;
    av_dict_set(&options,"rtsp_transport", "tcp", 0);
    av_dict_set(&options,"stimeout","10000",0);
    // 设置“buffer_size”缓存容量
    av_dict_set(&options, "buffer_size", "1024000", 0);

    int ret = avformat_open_input(&format_context_,addr.toStdString().c_str(),NULL,&options);
    if(ret< 0 )
    {
        qDebug() <<"[ERROR]:avformat_open_input FAIL..."<<endl;
        return ret;
    }
    //从媒体文件中读包进而获取流消息
    if(avformat_find_stream_info(format_context_,nullptr)<0)
    {
        //qDebug() <<"[ERROR]:avformat_find_stream_info FAIL..."<<endl;
        return -1;
    }
    //打印
    av_dump_format(format_context_,0,addr.toStdString().c_str(),0);

    for(unsigned int i=0;i<format_context_->nb_streams;i++)
    {
        video_st=format_context_->streams[i];
        //筛选视频流和音频流
        if(video_st->codecpar->codec_type==AVMEDIA_TYPE_VIDEO){
            video_stream_index_=i;
        }
        if(video_st->codecpar->codec_type==AVMEDIA_TYPE_AUDIO){
            audio_stream_index_=i;
        }
    }
    codecParam=format_context_->streams[video_stream_index_]->codecpar;   //获取编解码器的参数集
    codec_= const_cast<AVCodec*>(avcodec_find_decoder(codecParam->codec_id));   //获取编解码器
    if(NULL == codec_){
        qDebug()<<"获取编解码器 fail";
        return -1;
    }
    codecContext=avcodec_alloc_context3(nullptr);                               //获取编解码上下文
    avcodec_parameters_to_context(codecContext,codecParam);
    if( avcodec_open2(codecContext,codec_,nullptr)!=0)
    {
        avcodec_free_context(&codecContext);
        qDebug()<<"Error : can`t open codec";
        return -1;
    }

    //构造一个格式转换上下文
    y2r_sws_context_=sws_getContext(codecParam->width,codecParam->height,(AVPixelFormat)codecParam->format,
                                      codecParam->width,codecParam->height,
                                      AV_PIX_FMT_RGB32,SWS_BICUBIC,NULL,NULL,NULL);
    int bytes = av_image_get_buffer_size(AV_PIX_FMT_RGB32, codecContext->width, codecContext->height,4);

    m_OutBuffer = (uint8_t *)av_malloc(bytes * sizeof(uint8_t));
    avpicture_fill((AVPicture *)pFrameRGB, m_OutBuffer,
                   AV_PIX_FMT_RGB32, codecContext->width, codecContext->height);
    open_state_ = 1;
    return 1;
}

void Cc_Video_thread::run()
{
    while(true)
    {
        if(exit_state_ != 1)
        {
            break;
        }
        if(open_state_ == 1)
        { //6.读取数据包
          int ret=av_read_frame(format_context_,packet_);
            //if(ret<0)break;
           if(ret==0)
           {
            qDebug()<<"ret:"<<ret<<endl;
            char output[1024];
            if(packet_->stream_index==video_stream_index_)
            {
                video_frame_size+=packet_->size;
                memset(output,0,1024);
                sprintf(output,"recv %5d video frame %5d-%5d\n", ++video_frame_count, packet_->size, video_frame_size);
                qDebug()  << output;
                ret =avcodec_send_packet(codecContext,packet_);//packet中H264数据给解码器码器进行解码,解码好的YUV数据放在pInCodecCtx,
                // if(ret!=0)
                // {
                //     qDebug()<<"send packet error code is " <<ret;
                //     break;
                // }
                av_packet_unref(packet_);

                ret = avcodec_receive_frame(codecContext,yuv_frame_);//把解码好的YUV数据放到pFrame中

                if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
                    break;
                else if (ret < 0)
                {
                    qDebug()<<"Error during decoding\n";
                    break;
                }

                ret = sws_scale(y2r_sws_context_,yuv_frame_->data,yuv_frame_->linesize,0
                                ,codecParam->height,pFrameRGB->data,pFrameRGB->linesize);
                if(ret <= 0){
                    qDebug()  << "ERROR to rgb....";
                }
                // 转换到QImage
                QImage tmmImage((uchar *)m_OutBuffer, codecContext->width, codecContext->height, QImage::Format_RGB32);
                emit sendimage(tmmImage);
            }

            if(packet_->stream_index==audio_stream_index_)
            {
                audio_frame_size+=packet_->size;
                memset(output,0,1024);
                sprintf(output,"recv %5d audio frame %5d-%5d\n", ++audio_frame_count, packet_->size, audio_frame_size);
                qDebug()  << output;
            }
        }
        if(open_state_ != 1){
            qDebug() << "thread id is :"<< currentThreadId() <<"open_state_:" <<open_state_<<" run...";
            msleep(100);
        }
        }
    }
}

void Cc_Video_thread::exit()
{
    open_state_ = -1;
    exit_state_ = 0;
}

主线程:

  HttpFlvMainWindow.h

#ifndef HTTPFLVMAINWINDOW_H
#define HTTPFLVMAINWINDOW_H

#include <QMainWindow>
#include <QPixmap>
#include <QImage>
#include"cc_video_thread.h"
QT_BEGIN_NAMESPACE
namespace Ui {
class HttpFlvMainWindow;
}
QT_END_NAMESPACE

class HttpFlvMainWindow : public QMainWindow
{
    Q_OBJECT

public:
    HttpFlvMainWindow(QWidget *parent = nullptr);
    ~HttpFlvMainWindow();

private slots:
    void recvImage(QImage img);
private:
    Cc_Video_thread *videoThread=NULL;

private:
    Ui::HttpFlvMainWindow *ui;
};
#endif // HTTPFLVMAINWINDOW_H

   HttpFlvMainWindow.cpp

#include "httpflvmainwindow.h"
#include "ui_httpflvmainwindow.h"
#include <QDebug>
HttpFlvMainWindow::HttpFlvMainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::HttpFlvMainWindow)
{
    ui->setupUi(this);
    videoThread=new Cc_Video_thread();
    connect(videoThread,SIGNAL(sendimage(QImage)),this,SLOT(recvImage(QImage)));
}

HttpFlvMainWindow::~HttpFlvMainWindow()
{
    delete ui;
}

void HttpFlvMainWindow::recvImage(QImage img)
{
    //qDebug()<<"into recv"<<endl;
    QPixmap pixmap = QPixmap::fromImage(img);
    QPixmap fitpixmap = pixmap.scaled(ui->m_pLblVideo->width(), ui->m_pLblVideo->height(),
                                      Qt::IgnoreAspectRatio, Qt::SmoothTransformation);  // 饱满填充
    ui->m_pLblVideo->setPixmap(fitpixmap);
}

   最后在ui->m_pLblVideo->setPixmap(fitpixmap)显示画面信息

 经过测试:和webRTC进行比对,实时性和webRTC一致并且画质效果很好,几乎没有出现花屏的情况。

 打印的时间是系统的时间,电脑本身的计时器和标准北京时间慢:300ms,所以总的耗时在:100ms左右。

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

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

相关文章

Driver.js——实现页面引导

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1f4c3;个人状态&#xff1a; 研发工程师&#xff0c;现效力于中国工业软件事业 &#x1f680;人生格言&#xff1a; 积跬步…

TCP的流量控制深入理解

在理解流量控制之前我们先需要理解TCP的发送缓冲区和接收缓冲区&#xff0c;也称为套接字缓冲区。首先我们先知道缓冲区存在于哪个位置&#xff1f; 其中缓冲区存在于Socket Library层。 而我们的发送窗口和接收窗口就存在于缓冲区当中。在实现滑动窗口时则将两个指针指向缓冲区…

数据结构代码集训day10(适合考研、自学、期末和专升本)

习题均来自B站up&#xff1a;白话拆解数据结构&#xff01; 题目如下&#xff1a; &#xff08;1&#xff09;求两个链表的交集并存在第一个表中&#xff0c;注意俩链表是递增的且表示的是集合&#xff1b; &#xff08;2&#xff09;【408真题】假设该链表只给出了头指针 li…

C语言 | Leetcode C语言题解之第389题找不同

题目&#xff1a; 题解&#xff1a; char findTheDifference(char* s, char* t) {int n strlen(s), m strlen(t);int ret 0;for (int i 0; i < n; i) {ret ^ s[i];}for (int i 0; i < m; i) {ret ^ t[i];}return ret; }

自己动手写CPU_step6_算术运算指令

序 接上篇&#xff0c;本篇开始实现算数运算指令&#xff0c;包括加减乘除&#xff0c;加减比较好实现&#xff0c;乘除则需要考虑指令周期与其他指令的周期长度不一致问题&#xff0c;可能会导致流水线效率下降&#xff0c;本篇先实现简单的算术运算。 指令定义 define EXE_AD…

java epoll网络编程

java epoll网络编程 从通信开始 人类社会的发展离不开相互协作&#xff0c;一起围猎、抵御野兽&#xff0c;一起扛起锄头夯地、夯人&#xff0c;再到你与好兄弟之间征战峡谷。在这一切互相协作的背后&#xff0c;都离不开信息的传递&#xff0c;也就是通信。一群人聚在一起&am…

Android学习笔记(一) Android Studio 安装配置

大家好&#xff0c;我是半虹&#xff0c;这篇文章来讲 Android Studio 的安装配置 1、基本介绍 Android Studio 是 Google 推出的 Android 集成开发环境&#xff0c;可以用于创建、开发和调试 Android 项目 Android Studio 是基于 IntelliJ IDEA 开发的 &#xff0c;提供了专…

typora的一些配置方法

1.设置图片的保存路径 选择 文件 --> 偏好设置 --> 图像 --> 设置图片要保存的位置 将网络的图片保存到本地&#xff0c;例如&#xff0c;从csdn复制文章时&#xff0c;将文章中的图片自动保存到本地 在图像设置的插入图片时一栏&#xff0c;勾选对网络位置的图片应…

vmware中克隆过来的linux节点无system eth0

问题现象 使用vmware虚拟机的克隆功能后&#xff0c;找不到system eth0 解决办法 编辑/etc/udev/rules.d/70-persistent-net.rules文件 可以看到&#xff0c;eth0&#xff0c;是克隆前机器的网卡&#xff0c;eth1是克隆后机器生成的网卡&#xff0c;所以把NAME"eth0&q…

解决方案:在autodl环境下安装torch被killed掉

文章目录 一、现象二、解决方案 一、现象 平台&#xff1a;autodl 镜像&#xff1a;PyTorch 2.0.0 Python 3.8(ubuntu20.04) Cuda 11.8 GPU&#xff1a;A40(48GB) * 1 CPU&#xff1a;15 vCPU AMD EPYC 7543 32-Core Processor 内存&#xff1a;80GB 安装torch:1.13.0环境&a…

基于CloudflareSpeedTest项目实现git clone加速

1.网络测速 「自选优选 IP」测试 Cloudflare CDN 延迟和速度&#xff0c;获取最快 IP 更多内容参考项目&#xff1a;https://github.com/XIU2/CloudflareSpeedTest 国外很多网站都在使用 Cloudflare CDN&#xff0c;但分配给中国内地访客的 IP 并不友好&#xff08;延迟高、丢…

串口助手使用和插入usb转TTL的COM口识别问题

问题出现原因 由于串口调试中经常需要通过断电对单片机烧录程序&#xff0c;所以制作了一个转接带开关的USB 转接口&#xff0c;如下图所示&#xff0c;其中按键控制的是OUT口的电源通断。但为了能够数据传输&#xff0c;有两根传输数据的线是一直连接的。在使用usb进行程序烧…

【Threejs学习】创建Threejs页面

学习文档地址&#xff1a; threejs官网&#xff1a;https://threejs.org/ Threejs官网中文文档&#xff1a;https://threejs.org/docs/index.html#manual/zh/ threejs中文网&#xff1a;http://www.webgl3d.cn/ threejs基础教程&#xff1a;http://www.webgl3d.cn/pages/aac9ab…

《软件工程导论》(第6版)第6章 详细设计 复习笔记

第6章 详细设计 一、详细设计概述 1&#xff0e;目标 &#xff08;1&#xff09;详细设计阶段的根本目标是确定应该怎样具体地实现所要求的系统&#xff0c;即经过这个阶段的设计工作&#xff0c;应该得出对目标系统的精确描述&#xff0c;从而在编码阶段可以把这个描述直接…

四足机器人控制算法——建模、控制与实践(unitree_guide配置)

目录 官方文档 unitree_guide 1. 快捷指令 2. ROS安装 3. LCM库安装 3.1. 安装步骤 4. pthread库 5. 工程文件下载 6. 编译 报错&#xff1a; 报错1 报错2&#xff1a; 报错3 其他报错 7. 运行 7.1. 运行 Gazebo 仿真环境 7.2. 启动控制器 8. 简单使用 官方文…

贪心(临项交换)

前言&#xff1a;对待这个问题&#xff0c;我想到就是一定是贪心&#xff0c;但是我不知道怎么排序 对待这种问题&#xff0c;肯定是要先假设只有两个&#xff0c;我们要怎么排序呢 class Solution { public:long long minDamage(int power, vector<int>& damage, v…

分布式主键

目录 1.分布式主键的基本需求 2.常见的分布式主键生成策略 2.1UUID&#xff08;128位&#xff09; 2.2MySQL 2.2.1自增主键 2.2.2区间号段 2.3Redis 2.4SnowFlake雪花算法&#xff08;64位&#xff09; 1.分布式主键的基本需求 全局唯一&#xff1a;不管什么主键&…

Linux驱动(二):模块化编程的基本操作

目录 前言一、模块化编程1.模块化驱动代码框架2.printk详解3.应用操作 二、多模块编程三、多文件编程四、函数传参 前言 没多少东西&#xff0c;就是最基础的一些Linux驱动编写操作。 一、模块化编程 驱动加载到内核中的两种方法&#xff1a; 1.静态编译&#xff1a;就是将模…

【Python百日进阶-Web开发-Feffery】Day500 - dash使用秘籍

文章目录 前言:fac是什么?“人生苦短,我用Python;Web开发,首选Feffery!”↓↓↓ 今日笔记 ↓↓↓1 dash应用使用cdn加载静态资源1.1 页面效果1.2 项目源码2 suppress_callback_exceptions=True3 阻止首次回调3.1 阻止所有回调函数的首次回调3.2 阻止单个回调函数的首次回…

《JavaEE进阶》----5.<SpringMVC②剩余基本操作(CookieSessionHeader响应)>

Cookie和Session简介。 Spring MVC的 2.请求 Cookie的设置和两种获取方式 Session的设置和三种获取方式。 3.响应 1.返回静态页面 2.返回数据 3.返回HTML片段 4.返回JSON 5.设置状态码 6.设置header 三、&#xff08;接上文&#xff09;SpringMVC剩余基本操作 3.2postman请求 …