FFmpeg线程类封装+QT5实现直播拉流(含源代码)

C++编程FFMpeg实时美颜直播推流实战-基于ffmpeg,qt5,opencv视频课程-夏曹俊-专题视频课程... C++编程FFMpeg实时美颜直播推流实战视频培训教程,本课程基于ffmpeg,qt5,opencv进行实战教学。 基于c++编程,掌握录制视频(rtsp和系统相机)录制音频(qt)开发方法,掌握音视频各类参数含义,掌握音视频编码(h264+acc),磨皮美颜(opencv),音视频封装(flv),基于rtmp协议推流。多线程处理技术。掌握nginx-rtmp和crtmpserver的直播服务器... 阅读详情
  1. FFmpeg源码编译

参考:FFmpeg源码编译(基于MSVC编译器)

  1. Qt5使用FFmpeg

参考:Qt5 + FFmpeg

  1. FFmpegThread封装
    1. 初始化函数,只需要初始化一次即可。在最新的ffmpeg版本中,只需要初始化网络流格式。
//初始化(初始化一次即可)
void FFmpegThread::initlib() {
    static QMutex mutex;
    QMutexLocker locker(&mutex);
    static bool isInit = false;
    if (!isInit) {
        //注册所有设备,主要用于本地摄像机播放支持
#ifdef ffmpegdevice
        avdevice_register_all();
#endif
        //初始化网络流格式,使用网络流时必须先执行
        avformat_network_init();

        isInit = true;
        qDebug() << TIMEMS << "init ffmpeg lib ok" << " version:" << FFMPEG_VERSION;
    }
}

  1. 设置字典参数AVDictionary *options。在FFmpeg中,av_dict_set()函数用于向字典中添加键值对。AVDictionary是FFmpeg中用于存储元数据的一种数据结构,它基本上是一个简单的哈希表,用于存储字符串类型的键值对。它可以被用来传递多种数据,例如视频解码参数、音频参数、字幕参数等等。
//设置缓存大小,1080p可将值调大
av_dict_set(&options, "buffer_size", "8192000", 0);
//以tcp方式打开,如果以udp方式打开将tcp替换为udp
av_dict_set(&options, "rtsp_transport", "tcp", 0);
//设置超时断开连接时间,单位微秒,3000000表示3秒
av_dict_set(&options, "stimeout", "3000000", 0);
// ,单位微秒,1000000表示1秒
av_dict_set(&options, "max_delay", "1000000", 0);
//自动开启线程数
av_dict_set(&options, "threads", "auto", 0);
//zerolatency:转码延迟,以牺牲视频质量减少时延
av_dict_set(&options, "tune", "zerolatency", 0);
  1. 打开指定的URL输入流。 打开输入流并读取标题,编解码器此时不会打开。流必须使用avformat_close_input()关闭。
//打开视频流
avFormatContext = avformat_alloc_context();

int result = avformat_open_input(&avFormatContext, url.toStdString().data(), NULL, &options);
if (result < 0) {
    qDebug() << "open input error" << url;
    return false;
}
qDebug() << "open input:" << url << " " << result;
//释放设置参数
if (options != NULL) {
    av_dict_free(&options);
    qDebug() << "free option";
}
//获取流信息
result = avformat_find_stream_info(avFormatContext, NULL);
if (result < 0) {
    qDebug() << TIMEMS << "find stream info error";
    return false;
}
  1. 查找最佳匹配的媒体流,会返回找到的最佳匹配媒体流的索引号 。通过编解码器上下文的id查找解码器。
videoStreamIndex = av_find_best_stream(avFormatContext,
                                               AVMEDIA_TYPE_VIDEO,
                                               -1,
                                               -1,
                                               const_cast<const AVCodec **>(&videoDecoder),
                                               0);
if (videoStreamIndex < 0) {
    qDebug() << "find video stream index error";
    return false;
}

//获取视频流
AVStream *videoStream = avFormatContext->streams[videoStreamIndex];

//获取视频流解码器,或者指定解码器
videoCodec = avcodec_alloc_context3(NULL);
avcodec_parameters_to_context(videoCodec, videoStream->codecpar);
videoDecoder = const_cast<AVCodec *>(avcodec_find_decoder(videoCodec->codec_id));
//videoDecoder = avcodec_find_decoder_by_name("h264_qsv");
if (videoDecoder == NULL) {
    qDebug() << "video decoder not found";
    return false;
}

//设置加速解码
videoCodec->lowres = videoDecoder->max_lowres;
videoCodec->flags2 |= AV_CODEC_FLAG2_FAST;

//打开视频解码器
result = avcodec_open2(videoCodec, videoDecoder, NULL);
if (result < 0) {
    qDebug() << "open video codec error";
    return false;
}

//获取分辨率大小
videoWidth = videoStream->codecpar->width;
videoHeight = videoStream->codecpar->height;

//如果没有获取到宽高则返回
if (videoWidth == 0 || videoHeight == 0) {
    qDebug() << "find width height error";
    return false;
}

QString videoInfo = QString(u8"视频流信息 -> 索引: %1  解码: %2  格式: %3  时长: %4 秒  分辨率: %5*%6")
        .arg(videoStreamIndex)
        .arg(videoDecoder->name, avFormatContext->iformat->name)
        .arg((avFormatContext->duration) / 1000000, videoWidth, videoHeight);

qDebug() << videoInfo;
  1. 以类似同样的方式找到音频流解码器。
//循环查找音频流索引
audioStreamIndex = -1;
for (uint i = 0; i < avFormatContext->nb_streams; i++) {
    if (avFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
        audioStreamIndex = i;
        break;
    }
}

//有些没有音频流,所以这里不用返回
if (audioStreamIndex == -1) {
    qDebug() << "find audio stream index error";
} else {
    //获取音频流
    AVStream *audioStream = avFormatContext->streams[audioStreamIndex];
    audioCodec = avcodec_alloc_context3(NULL);
    avcodec_parameters_to_context(audioCodec, audioStream->codecpar);

    //获取音频流解码器,或者指定解码器
    audioDecoder = const_cast<AVCodec *>(avcodec_find_decoder(audioCodec->codec_id));
    //audioDecoder = avcodec_find_decoder_by_name("aac");
    if (audioDecoder == NULL) {
        qDebug() << "audio codec not found";
        return false;
    }

    //打开音频解码器
    result = avcodec_open2(audioCodec, audioDecoder, NULL);
    if (result < 0) {
        qDebug() << "open audio codec error";
        return false;
    }

    QString audioInfo = QString(u8"音频流信息 -> 索引: %1  解码: %2  比特率: %3  声道数: %4  采样: %5")
            .arg(audioStreamIndex)
            .arg(audioDecoder->name)
            .arg(avFormatContext->bit_rate, audioCodec->channels, audioCodec->sample_rate);
    qDebug() << audioInfo;
}

  1. 分配AVPacket内存并将其字段设置为默认值。由此产生的struct必须使用av_packet_free()释放。然后进行图像的转换。
//预分配好内存
avPacket = av_packet_alloc();
avFrame = av_frame_alloc();
avFrame2 = av_frame_alloc();
avFrame3 = av_frame_alloc();

//比较上一次文件的宽度高度,当改变时,需要重新分配内存
if (oldWidth != videoWidth || oldHeight != videoHeight) {
    int byte = av_image_get_buffer_size(AV_PIX_FMT_RGB32, videoWidth, videoHeight, 1);
    buffer = (uint8_t *) av_malloc(byte * sizeof(uint8_t));
    oldWidth = videoWidth;
    oldHeight = videoHeight;
}

//定义像素格式
AVPixelFormat srcFormat = AV_PIX_FMT_YUV420P;
AVPixelFormat dstFormat = AV_PIX_FMT_RGB32;
//通过解码器获取解码格式
srcFormat = videoCodec->pix_fmt;

//默认最快速度的解码采用的SWS_FAST_BILINEAR参数,可能会丢失部分图片数据,可以自行更改成其他参数
int flags = SWS_FAST_BILINEAR;

//开辟缓存存储一帧数据
//以下两种方法都可以,avpicture_fill已经逐渐被废弃
//avpicture_fill((AVPicture *)avFrame3, buffer, dstFormat, videoWidth, videoHeight);
av_image_fill_arrays(avFrame3->data, avFrame3->linesize, buffer,
                     dstFormat, videoWidth, videoHeight, 1);

//图像转换
swsContext = sws_getContext(videoWidth, videoHeight, srcFormat,
                            videoWidth, videoHeight, dstFormat,
                            flags, NULL, NULL, NULL);
  1. 子线程循环对帧数据处理,判断当前处理的包时视频还是音频(此案例只处理视频数据)。如果是视频数据,则将其转化为图像QImage,在通过本次解析出来的时间戳减去上一帧的时间戳得到需要延时的时间间隔。
void FFmpegThread::run() {
    // qint64 startTime = av_gettime();
    while (!stopped) {
        //根据标志位执行初始化操作
        if (isPlay) {
            bool ret = this->init();
            if (!ret) {
                emit signal_initFailed();
                stopped = !stopped;
                break;
            }
            isPlay = false;
            continue;
        }

        frameFinish = av_read_frame(avFormatContext, avPacket);
        if (frameFinish >= 0) {
            //判断当前包是视频还是音频
            int index = avPacket->stream_index;
            if (index == videoStreamIndex) {

                frameFinish = avcodec_send_packet(videoCodec, avPacket);
                if (frameFinish < 0) {
                    continue;
                }

                frameFinish = avcodec_receive_frame(videoCodec, avFrame2);
                if (frameFinish < 0) {
                    continue;
                }

                if (frameFinish >= 0) {
                    //将数据转成一张图片
                    sws_scale(swsContext, (const uint8_t *const *) avFrame2->data, avFrame2->linesize, 0,
                              videoHeight, avFrame3->data, avFrame3->linesize);

                    //以下两种方法都可以
                    //QImage image(avFrame3->data[0], videoWidth, videoHeight, QImage::Format_RGB32);
                    QImage image((uchar *) buffer, videoWidth, videoHeight, QImage::Format_RGB32);
                    if (!image.isNull()) {
                        emit signal_receiveImage(image);
                    }
                    usleep(1);
                }
#if 0
                //延时(不然文件会立即全部播放完)
                AVRational timeBase = {1, AV_TIME_BASE};

                int64_t ptsTime = av_rescale_q(avPacket->dts, avFormatContext->streams[videoStreamIndex]->time_base, timeBase);

                // 微秒
                int64_t nowTime = av_gettime() - startTime;
                qDebug() << "ptstime:" << ptsTime << "nowTime:" << nowTime;
                if (ptsTime > nowTime) {
                    av_usleep(ptsTime - nowTime);
                }
#else
                AVRational timeBase = {1, AV_TIME_BASE};
                int64_t ptsTime = av_rescale_q(avPacket->dts,
                                               avFormatContext->streams[videoStreamIndex]->time_base,
                                               timeBase);
                currentTime = ptsTime;
                if (lastTime == 0) {
                    av_usleep(10 * 1000);
                }
                if ((currentTime - lastTime) > 1 * 1000 * 1000 || (currentTime - lastTime) < 0) {
                    av_usleep(10 * 1000);
                } else {
                    av_usleep(currentTime - lastTime);
                }
                lastTime = currentTime;
#endif
            } else if (index == audioStreamIndex) {
                //解码音频流,自行处理
            }
        }

        av_packet_unref(avPacket);
        av_freep(avPacket);
        usleep(1);
    }

    //线程结束后释放资源
    free();
    stopped = false;
    isPlay = false;
    qDebug() << "stop ffmpeg thread";
    emit signal_threadOver();
}
  1. FFmpegWidget封装
    1. 构造函数,实例化FFmpeg子线程,处理子线程中处理完图像的型号,以及初始化失败的信号。
FFmpegWidget::FFmpegWidget(QWidget *parent) : QWidget(parent) {
    thread = new FFmpegThread(this);
    connect(thread, SIGNAL(signal_receiveImage(QImage)), this, SLOT(updateImage(QImage)));
    image = QImage();
    connect(thread, &FFmpegThread::signal_initFailed, this, &FFmpegWidget::signal_openVideoFailed);
}
void FFmpegWidget::updateImage(const QImage &image) {
    this->image = image;
    this->update();
}
  1. 重写paintEvent,将从FFmpeg子线程中接收到的图像数据绘制在QWidget窗口中。
void FFmpegWidget::paintEvent(QPaintEvent *) {
    if (image.isNull()) {
        return;
    }

    QPainter painter(this);
#if 0
    //image = image.scaled(this->size(), Qt::KeepAspectRatio);
    //按照比例自动居中绘制
    int pixX = rect().center().x() - image.width() / 2;
    int pixY = rect().center().y() - image.height() / 2;
    QPoint point(pixX, pixY);
    painter.drawImage(point, image);
#else
    painter.drawImage(this->rect(), image);
#endif
}
  1. 启动/停止视频。通过控制FFmpeg子线程中的循环标志来控制视频播放。
void FFmpegWidget::open() {
    qDebug() << TIMEMS << "open video" << objectName();
    clear();
    thread->play();
    thread->start();
}

void FFmpegWidget::close() {
    qDebug() << TIMEMS << "close video" << objectName();
    if (thread->isRunning()) {
        thread->stop();
        thread->quit();
        thread->wait(500);
    }
    QTimer::singleShot(1, this, SLOT(clear()));
}
  1. Qt调用

在QT中只需要在ui文件中添加一个QWidget,并将这个QWidget部件提升为FFmpegWidget即可,通过调用FFmpegWidget的setUrl()方法设置拉流地址,然后通过open()和close()方法启动和停止视频。

  1. 源代码
    1. ffmpeginclude.h
#ifndef FFMPEGINCLUDE_H
#define FFMPEGINCLUDE_H

//为了兼容C和C99标准必须加以下内容(否则编译不能通过)
#ifndef INT64_C
#define INT64_C
#define UINT64_C
#endif

//引入ffmpeg头文件
extern "C" {
#include "libavutil/opt.h"
#include "libavutil/time.h"
#include "libavutil/frame.h"
#include "libavutil/display.h"
#include "libavutil/pixdesc.h"
#include "libavutil/avassert.h"
#include "libavutil/imgutils.h"
#include "libavutil/ffversion.h"
#include "libavcodec/avcodec.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
#include "libavformat/avformat.h"
#include "libavfilter/avfilter.h"
#include "libavfilter/buffersink.h"
#include "libavfilter/buffersrc.h"
#include "libavdevice/avdevice.h"
#if (LIBAVCODEC_VERSION_MAJOR > 56)
#include "libavutil/hwcontext.h"
#endif
}

#include <QObject>
#include <QDateTime>
#include <QDebug>

#ifdef Q_CC_MSVC
#pragma execution_character_set("utf-8")
#endif

#ifndef TIMEMS
#define TIMEMS qPrintable(QTime::currentTime().toString("HH:mm:ss")  + "[" + __FUNCTION__ + ":" + QString::number(__LINE__ ) + "]")
#endif

//通过avcode版本定义对应主版本
#if (LIBAVCODEC_VERSION_MAJOR == 56)
#define FFMPEG_VERSION_MAJOR 2
#elif (LIBAVCODEC_VERSION_MAJOR == 57)
#define FFMPEG_VERSION_MAJOR 3
#elif (LIBAVCODEC_VERSION_MAJOR == 58)
#define FFMPEG_VERSION_MAJOR 4
#elif (LIBAVCODEC_VERSION_MAJOR == 59)
#define FFMPEG_VERSION_MAJOR 5
#elif (LIBAVCODEC_VERSION_MAJOR == 60)
#define FFMPEG_VERSION_MAJOR 6
#endif

#if (FFMPEG_VERSION_MAJOR > 4)
#define AVCodecx const AVCodec
#define AVInputFormatx const AVInputFormat
#define AVOutputFormatx const AVOutputFormat
#else
#define AVCodecx AVCodec
#define AVInputFormatx AVInputFormat
#define AVOutputFormatx AVOutputFormat
#endif

#if (FFMPEG_VERSION_MAJOR < 3)
enum AVHWDeviceType {
    AV_HWDEVICE_TYPE_VDPAU,
    AV_HWDEVICE_TYPE_CUDA,
    AV_HWDEVICE_TYPE_VAAPI,
    AV_HWDEVICE_TYPE_DXVA2,
    AV_HWDEVICE_TYPE_QSV,
    AV_HWDEVICE_TYPE_VIDEOTOOLBOX,
    AV_HWDEVICE_TYPE_NONE,
    AV_HWDEVICE_TYPE_D3D11VA,
    AV_HWDEVICE_TYPE_DRM,
};
#endif

#endif // FFMPEGINCLUDE_H

  1. ffmpeg.h
#ifndef FFMPEG_H
#define FFMPEG_H

#include <QtGui>
#include <QWidget>
#include "ffmpeginclude.h"

class FFmpegThread : public QThread
{
    Q_OBJECT
public:
    explicit FFmpegThread(QObject *parent = 0);
    /// 初始化
    static void initlib();

protected:
    void run();

private:
    /// 线程停止标志位
    volatile bool stopped = false;
    /// 播放视频标志位
    volatile bool isPlay = false;
    /// 一帧完成
    int frameFinish = 0;
    /// 视频宽度
    int videoWidth = 0;
    /// 视频高度
    int videoHeight = 0;
    /// 上一次视频宽度
    int oldWidth = 0;
    /// 上一次视频高度
    int oldHeight = 0;
    /// 视频流索引
    int videoStreamIndex = -1;
    /// 音频流索引
    int audioStreamIndex = -1;

    /// 视频流地址
    QString url = "";
    /// 存储解码后图片buffer
    uint8_t *buffer;
    /// 包对象
    AVPacket *avPacket;
    /// 帧对象
    AVFrame *avFrame;
    /// 帧对象
    AVFrame *avFrame2;
    /// 帧对象
    AVFrame *avFrame3;
    /// 格式对象
    AVFormatContext *avFormatContext;
    /// 视频解码器
    AVCodecContext *videoCodec;
    /// 音频解码器
    AVCodecContext *audioCodec;
    /// 处理图片数据对象
    SwsContext *swsContext;

    /// 参数对象
    AVDictionary *options;
    /// 视频解码
    AVCodec *videoDecoder;
    /// 音频解码
    AVCodec *audioDecoder;

    /// 记录上一次时间戳
    qint64 lastTime = 0;
    /// 记录当前时间戳
    qint64 currentTime = 0;

signals:
    /// 信号:收到图片信号
    void signal_receiveImage(const QImage &image);

    /// 信号:初始化失败
    void signal_initFailed();

    /// 信号:线程结束
    void signal_threadOver();

public slots:
    /// 设置视频流地址
    void setUrl(const QString &url);

    /// 初始化视频对象
    bool init();

    /// 释放对象
    void free();

    /// 播放视频对象
    void play();

    /// 暂停播放
    void pause();

    /// 继续播放
    void next();

    /// 停止采集线程
    void stop();
};

//实时视频显示窗体类
class FFmpegWidget : public QWidget
{
    Q_OBJECT
public:
    explicit FFmpegWidget(QWidget *parent = 0);
    ~FFmpegWidget();

protected:   
    void paintEvent(QPaintEvent *);

private:
    FFmpegThread *thread;
    QImage image;

signals:
    /// 信号:打开视频失败
    void signal_openVideoFailed();

private slots:
    /// 接收图像并绘制
    void updateImage(const QImage &image);

public slots:
    /// 设置视频流地址
    void setUrl(const QString &url);

    /// 打开设备
    void open();

    /// 暂停
    void pause();

    /// 继续
    void next();

    /// 关闭设备
    void close();

    ///清空
    void clear();
};

#endif // FFMPEG_H

  1. ffmpeg.cpp
#include "ffmpeg.h"

FFmpegThread::FFmpegThread(QObject *parent) : QThread(parent) {
    setObjectName("FFmpegThread");

    buffer = NULL;
    avPacket = NULL;
    avFrame = NULL;
    avFrame2 = NULL;
    avFrame3 = NULL;
    avFormatContext = NULL;
    videoCodec = NULL;
    audioCodec = NULL;
    swsContext = NULL;

    options = NULL;
    videoDecoder = NULL;
    audioDecoder = NULL;

    //初始化注册(注册一次即可)
    FFmpegThread::initlib();
}

//初始化(初始化一次即可)
void FFmpegThread::initlib() {
    static QMutex mutex;
    QMutexLocker locker(&mutex);
    static bool isInit = false;
    if (!isInit) {
        //注册所有设备,主要用于本地摄像机播放支持
#ifdef ffmpegdevice
        avdevice_register_all();
#endif
        //初始化网络流格式,使用网络流时必须先执行
        avformat_network_init();

        isInit = true;
        qDebug() << TIMEMS << "init ffmpeg lib ok" << " version:" << FFMPEG_VERSION;
    }
}

bool FFmpegThread::init() {
    //在打开码流前指定各种参数比如:探测时间/超时时间/最大延时等
    //设置缓存大小,1080p可将值调大
    av_dict_set(&options, "buffer_size", "8192000", 0);
    //以tcp方式打开,如果以udp方式打开将tcp替换为udp
    av_dict_set(&options, "rtsp_transport", "tcp", 0);
    //设置超时断开连接时间,单位微秒,3000000表示3秒
    av_dict_set(&options, "stimeout", "3000000", 0);
    // ,单位微秒,1000000表示1秒
    av_dict_set(&options, "max_delay", "1000000", 0);
    //自动开启线程数
    av_dict_set(&options, "threads", "auto", 0);
    //zerolatency:转码延迟,以牺牲视频质量减少时延
    av_dict_set(&options, "tune", "zerolatency", 0);
    //打开视频流
    avFormatContext = avformat_alloc_context();

    int result = avformat_open_input(&avFormatContext, url.toStdString().data(), NULL, &options);
    if (result < 0) {
        qDebug() << TIMEMS << "open input error" << url;
        return false;
    }
    qDebug() << TIMEMS << "open input:" << url << " " << result;
    //释放设置参数
    if (options != NULL) {
        av_dict_free(&options);
        qDebug() << TIMEMS << "free option";
    }

    //获取流信息
    result = avformat_find_stream_info(avFormatContext, NULL);
    if (result < 0) {
        qDebug() << TIMEMS << "find stream info error";
        return false;
    }

    //----------视频流部分开始,打个标记方便折叠代码----------
    if (1) {
        videoStreamIndex = av_find_best_stream(avFormatContext,
                                               AVMEDIA_TYPE_VIDEO,
                                               -1,
                                               -1,
                                               const_cast<const AVCodec **>(&videoDecoder),
                                               0);
        if (videoStreamIndex < 0) {
            qDebug() << TIMEMS << "find video stream index error";
            return false;
        }

        //获取视频流
        AVStream *videoStream = avFormatContext->streams[videoStreamIndex];

        //获取视频流解码器,或者指定解码器
        videoCodec = avcodec_alloc_context3(NULL);
        avcodec_parameters_to_context(videoCodec, videoStream->codecpar);
        videoDecoder = const_cast<AVCodec *>(avcodec_find_decoder(videoCodec->codec_id));
        //videoDecoder = avcodec_find_decoder_by_name("h264_qsv");
        if (videoDecoder == NULL) {
            qDebug() << TIMEMS << "video decoder not found";
            return false;
        }

        //设置加速解码
        videoCodec->lowres = videoDecoder->max_lowres;
        videoCodec->flags2 |= AV_CODEC_FLAG2_FAST;

        //打开视频解码器
        result = avcodec_open2(videoCodec, videoDecoder, NULL);
        if (result < 0) {
            qDebug() << TIMEMS << "open video codec error";
            return false;
        }

        //获取分辨率大小
        videoWidth = videoStream->codecpar->width;
        videoHeight = videoStream->codecpar->height;

        //如果没有获取到宽高则返回
        if (videoWidth == 0 || videoHeight == 0) {
            qDebug() << TIMEMS << "find width height error";
            return false;
        }

        QString videoInfo = QString(u8"视频流信息 -> 索引: %1  解码: %2  格式: %3  时长: %4 秒  分辨率: %5*%6")
                .arg(videoStreamIndex)
                .arg(videoDecoder->name, avFormatContext->iformat->name)
                .arg((avFormatContext->duration) / 1000000, videoWidth, videoHeight);

        qDebug() << TIMEMS << videoInfo;
    }
    //----------视频流部分开始----------

    //----------音频流部分开始,打个标记方便折叠代码----------
    if (1) {
        //循环查找音频流索引
        audioStreamIndex = -1;
        for (uint i = 0; i < avFormatContext->nb_streams; i++) {
            if (avFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
                audioStreamIndex = i;
                break;
            }
        }

        //有些没有音频流,所以这里不用返回
        if (audioStreamIndex == -1) {
            qDebug() << TIMEMS << "find audio stream index error";
        } else {
            //获取音频流
            AVStream *audioStream = avFormatContext->streams[audioStreamIndex];
            audioCodec = avcodec_alloc_context3(NULL);
            avcodec_parameters_to_context(audioCodec, audioStream->codecpar);

            //获取音频流解码器,或者指定解码器
            audioDecoder = const_cast<AVCodec *>(avcodec_find_decoder(audioCodec->codec_id));
            //audioDecoder = avcodec_find_decoder_by_name("aac");
            if (audioDecoder == NULL) {
                qDebug() << TIMEMS << "audio codec not found";
                return false;
            }

            //打开音频解码器
            result = avcodec_open2(audioCodec, audioDecoder, NULL);
            if (result < 0) {
                qDebug() << TIMEMS << "open audio codec error";
                return false;
            }

            QString audioInfo = QString(u8"音频流信息 -> 索引: %1  解码: %2  比特率: %3  声道数: %4  采样: %5")
                    .arg(audioStreamIndex)
                    .arg(audioDecoder->name)
                    .arg(avFormatContext->bit_rate, audioCodec->channels, audioCodec->sample_rate);
            qDebug() << TIMEMS << audioInfo;
        }
    }
    //----------音频流部分结束----------

    //预分配好内存
    avPacket = av_packet_alloc();
    avFrame = av_frame_alloc();
    avFrame2 = av_frame_alloc();
    avFrame3 = av_frame_alloc();

    //比较上一次文件的宽度高度,当改变时,需要重新分配内存
    if (oldWidth != videoWidth || oldHeight != videoHeight) {
        int byte = av_image_get_buffer_size(AV_PIX_FMT_RGB32, videoWidth, videoHeight, 1);
        buffer = (uint8_t *) av_malloc(byte * sizeof(uint8_t));
        oldWidth = videoWidth;
        oldHeight = videoHeight;
    }

    //定义像素格式
    AVPixelFormat srcFormat = AV_PIX_FMT_YUV420P;
    AVPixelFormat dstFormat = AV_PIX_FMT_RGB32;
    //通过解码器获取解码格式
    srcFormat = videoCodec->pix_fmt;

    //默认最快速度的解码采用的SWS_FAST_BILINEAR参数,可能会丢失部分图片数据,可以自行更改成其他参数
    int flags = SWS_FAST_BILINEAR;

    //开辟缓存存储一帧数据
    //以下两种方法都可以,avpicture_fill已经逐渐被废弃
    //avpicture_fill((AVPicture *)avFrame3, buffer, dstFormat, videoWidth, videoHeight);
    av_image_fill_arrays(avFrame3->data, avFrame3->linesize, buffer,
                         dstFormat, videoWidth, videoHeight, 1);

    //图像转换
    swsContext = sws_getContext(videoWidth, videoHeight, srcFormat,
                                videoWidth, videoHeight, dstFormat,
                                flags, NULL, NULL, NULL);

    //输出视频信息
    //av_dump_format(avFormatContext, 0, url.toStdString().data(), 0);
    qDebug() << TIMEMS << "init ffmpeg finsh";
    return true;
}

void FFmpegThread::run() {
    // qint64 startTime = av_gettime();
    while (!stopped) {
        //根据标志位执行初始化操作
        if (isPlay) {
            bool ret = this->init();
            if (!ret) {
                emit signal_initFailed();
                stopped = !stopped;
                break;
            }
            isPlay = false;
            continue;
        }

        frameFinish = av_read_frame(avFormatContext, avPacket);
        if (frameFinish >= 0) {
            //判断当前包是视频还是音频
            int index = avPacket->stream_index;
            if (index == videoStreamIndex) {

                frameFinish = avcodec_send_packet(videoCodec, avPacket);
                if (frameFinish < 0) {
                    continue;
                }

                frameFinish = avcodec_receive_frame(videoCodec, avFrame2);
                if (frameFinish < 0) {
                    continue;
                }

                if (frameFinish >= 0) {
                    //将数据转成一张图片
                    sws_scale(swsContext, (const uint8_t *const *) avFrame2->data, avFrame2->linesize, 0,
                              videoHeight, avFrame3->data, avFrame3->linesize);

                    //以下两种方法都可以
                    //QImage image(avFrame3->data[0], videoWidth, videoHeight, QImage::Format_RGB32);
                    QImage image((uchar *) buffer, videoWidth, videoHeight, QImage::Format_RGB32);
                    if (!image.isNull()) {
                        emit signal_receiveImage(image);
                    }
                    usleep(1);
                }
#if 0
                //延时(不然文件会立即全部播放完)
                AVRational timeBase = {1, AV_TIME_BASE};

                int64_t ptsTime = av_rescale_q(avPacket->dts, avFormatContext->streams[videoStreamIndex]->time_base, timeBase);

                // 微秒
                int64_t nowTime = av_gettime() - startTime;
                qDebug() << "ptstime:" << ptsTime << "nowTime:" << nowTime;
                if (ptsTime > nowTime) {
                    av_usleep(ptsTime - nowTime);
                }
#else
                AVRational timeBase = {1, AV_TIME_BASE};
                int64_t ptsTime = av_rescale_q(avPacket->dts,
                                               avFormatContext->streams[videoStreamIndex]->time_base,
                                               timeBase);
                currentTime = ptsTime;
                if (lastTime == 0) {
                    av_usleep(10 * 1000);
                }
                if ((currentTime - lastTime) > 1 * 1000 * 1000 || (currentTime - lastTime) < 0) {
                    av_usleep(10 * 1000);
                } else {
                    av_usleep(currentTime - lastTime);
                }
                lastTime = currentTime;
#endif
            } else if (index == audioStreamIndex) {
                //解码音频流,自行处理
            }
        }

        av_packet_unref(avPacket);
        av_freep(avPacket);
        usleep(1);
    }

    //线程结束后释放资源
    free();
    stopped = false;
    isPlay = false;
    qDebug() << TIMEMS << "stop ffmpeg thread";
    emit signal_threadOver();
}

void FFmpegThread::setUrl(const QString &url) {
    this->url = url;
}

void FFmpegThread::free() {
    if (swsContext != NULL) {
        sws_freeContext(swsContext);
        swsContext = NULL;
    }

    if (avPacket != NULL) {
        av_packet_unref(avPacket);
        avPacket = NULL;
    }

    if (avFrame != NULL) {
        av_frame_free(&avFrame);
        avFrame = NULL;
    }

    if (avFrame2 != NULL) {
        av_frame_free(&avFrame2);
        avFrame2 = NULL;
    }

    if (avFrame3 != NULL) {
        av_frame_free(&avFrame3);
        avFrame3 = NULL;
    }

    if (videoCodec != NULL) {
        avcodec_close(videoCodec);
        videoCodec = NULL;
    }

    if (audioCodec != NULL) {
        avcodec_close(audioCodec);
        audioCodec = NULL;
    }

    if (avFormatContext != NULL) {
        avformat_close_input(&avFormatContext);
        avFormatContext = NULL;
    }

    av_dict_free(&options);
    qDebug() << TIMEMS << "close ffmpeg ok";
}

void FFmpegThread::play() {
    //通过标志位让线程执行初始化
    isPlay = true;
}

void FFmpegThread::pause() {

}

void FFmpegThread::next() {

}

void FFmpegThread::stop() {
    //通过标志位让线程停止
    stopped = true;
}

//实时视频显示窗体类
FFmpegWidget::FFmpegWidget(QWidget *parent) : QWidget(parent) {
    thread = new FFmpegThread(this);
    connect(thread, SIGNAL(signal_receiveImage(QImage)), this, SLOT(updateImage(QImage)));
    image = QImage();
    connect(thread, &FFmpegThread::signal_initFailed, this, &FFmpegWidget::signal_openVideoFailed);
}

FFmpegWidget::~FFmpegWidget() {
    close();
}

void FFmpegWidget::paintEvent(QPaintEvent *) {
    if (image.isNull()) {
        return;
    }

    QPainter painter(this);
#if 0
    //image = image.scaled(this->size(), Qt::KeepAspectRatio);
    //按照比例自动居中绘制
    int pixX = rect().center().x() - image.width() / 2;
    int pixY = rect().center().y() - image.height() / 2;
    QPoint point(pixX, pixY);
    painter.drawImage(point, image);
#else
    painter.drawImage(this->rect(), image);
#endif
}

void FFmpegWidget::updateImage(const QImage &image) {
    this->image = image;
    this->update();
}

void FFmpegWidget::setUrl(const QString &url) {
    thread->setUrl(url);
}

void FFmpegWidget::open() {
    qDebug() << TIMEMS << "open video" << objectName();
    clear();
    thread->play();
    thread->start();
}

void FFmpegWidget::pause() {
    thread->pause();
}

void FFmpegWidget::next() {
    thread->next();
}

void FFmpegWidget::close() {
    qDebug() << TIMEMS << "close video" << objectName();
    if (thread->isRunning()) {
        thread->stop();
        thread->quit();
        thread->wait(500);
    }
    QTimer::singleShot(1, this, SLOT(clear()));
}

void FFmpegWidget::clear() {
    image = QImage();
    update();
}

音视频学习 - 创建QT + ffmpeg项目进行视频解析 在mainwindow.ui的设计界面,拖一个Widget到主界面,然后在Widget上点击右键,然后选择提升为,在提升的类名称处输入上面自定义控件的类名。如果选择下面的全局包含,就不用再单独包含头文件了。 阅读详情

相关推荐

Qt实现RTMP拉流,实时显示视频流(附完整源码)

Qt实现RTMP拉流,实时显示视频流(附完整源码)

希望我的博客,能帮上你解决学习中工作中所遇到的问题 1138

QT+FFMPEG 从摄像机拉取rtsp裸h264流,播放并保存到本地

QT+FFMPEG 从摄像机拉取rtsp裸h264流,播放并保存到本地

asdasfdgdhh的博客 2069

Qt+ffmpeg实现视频流播放

1.版本 Qt版本:5.11 64位 ffmpeg版本:>3.064位 2.实现思路 ffmpeg拉流、解码,因为Qt不支持显示yuv数据,需要ffmpeg转换为rgb数据,再用QImage显示,这种方式性能会不好,暂时先实现这种简单的。一般情况下会用opengl渲染。 3.示例 拉流cctv1:http://ivi.bupt.edu.cn/hls/cctv1hd.m3u8,此例子只解码视频数据,不处理音频。 代码:解码线程 #pragma once #include &lt.

希望能帮助大家,希望大家多多支持,你们的支持是我前进的动力。 6777

Qt实现 基于ffmpeg拉流播放视频

1、前言 ffmpeg作为开源库,具备跨平台性,被广泛使用于各大视频软件和网站,在视音频开发中占有极其重要的地位。 Qt同样支持跨平台,因此结合qt+ffmpeg制作跨平台视频播放器是比较合适的做法。 2、思路 目前有一个界面类和解码类,其中界面类负责视频的显示工作,解码类的解码过程运行在子线程中,避免解码在主线程中进行,影响界面播放效果。二者的信息交互通过qt的信号槽关联,视频的绘制则采用QPainter绘制。 3、采用技术如下 1、QThread线程类 2、QPainter绘制类 3、F

c_shell_python的博客 5922

QT+FFMPEG推流客户端

本项目旨在介绍如何在QT框架下使用FFMPEG工具开发桌面推流客户端,实现将桌面画面实时推送到流媒体服务器。该项目将详细讲解推流原理、应用场景、算法实现、代码示例、部署测试、文献资料、应用产品、项目总结、影响分析和未来扩展方向。QT+FFMPEG推流客户端技术可以简单易用、灵活可控、成本低廉等优点,可以广泛应用于网络直播、远程监控、在线教学、游戏直播等场景。随着技术的不断发展和完善,QT+FFMPEG推流客户端技术将发挥更大的作用,推动相关产业的发展。

走向CTO的路上... 1786

QT软件开发-基于FFMPEG设计录屏与rtsp、rtmp推流软件(支持桌面与摄像头)(四)

前面几篇文章里完成了摄像头数据采集,桌面数据采集、麦克风音频数据采集,通过ffmpeg编码后保存视频,实现了基本的视频录制功能。当前文章在代码里增加推流功能,支持将编码好的数据通过rtmp、rtsp协议推流到流媒体服务器上,实现直播功能。

2990

FFmpeg线程类封装+QT5实现直播拉流(文末含源代码)

FFmpeg线程类封装+QT5实现直播拉流(文末含源代码)

ever__ever的博客 1218

基于 Qt C++ 的直播推流工具

qDebug() << "编码视频包:" << m_videoPacket->size << "字节,pts:" << m_videoPacket->pts;// ---------------------- 初始化视频编码器 ----------------------// ---------------------- 初始化音频编码器 ----------------------qDebug() << "收到 RTMP 响应:" << response.size() << "字节";

寒寒豆豆软件公司的博客 80

FFmpeg线程类封装及在Qt中的完整调用示例:直播拉流技术的Qt应用新篇章

FFmpeg线程类封装及在Qt中的完整调用示例:直播拉流技术的Qt应用新篇章 去发现同类优质开源项目:https://gitcode.com/ 项目介绍 在当今多媒体应用日益丰富的背景下,直播拉流技术已经成为许多软件开发不可或缺的功能。本文将为您介绍一个Qt框架下使用FFmpeg库的直播拉流代码示例——FFmpeg线程类封装及在Qt中的完整调用示例。该示例为开发者提供了一个简洁、高效的直播流处理方...

gitblog_06738的博客 825

qt+ffmpeg 实现音视频播放(一)之交叉编译

音视频编解码库,提供了多种编解码器,可以支持多种音视频格式的编解码操作。:音视频封装和解封装库,提供了多种封装格式的支持,可以读取和写入多种音视频文件格式。:音视频过滤器库,提供了多种音视频过滤器,可以对音视频流进行各种处理,如添加水印、调整亮度、对比度等。:音视频输入输出设备库,提供了多种输入输出设备的支持,如摄像头、麦克风等。:视频图像缩放库,提供了多种图像缩放算法,可以对视频图像进行缩放操作。:音频重采样库,提供了多种音频重采样算法,可以对音频进行重采样操作。

ckg3824278的博客 2047

qt使用ffmpeg api进行摄像头视频流的读取,推流,拉流,视频播放等功能的实现

qt使用ffmpeg api进行摄像头视频流的读取,推流,拉流,视频播放等功能的实现。

视频教程-C++编程FFMpeg(QT5+OpenCV)实战--实时美颜直播推流-C/C++

C++编程FFMpeg(QT5+OpenCV)实战--实时美颜直播推流 夏曹...

weixin_30041685的博客 1857

silvaco例子库学习—mos1ex02

mos1ex02相比于第一个例子,主要进行了mos的输出特性仿真,工艺结构是一样的,所以就只简单讨论一下atlas特性仿真的部分。

weixin_61913839的博客 852

[笔记]Qt5+FFMpeg+Opencv 实现实时美颜直播推流《一》基础知识以及直播服务器配置

文章目录前言一、基础知识1.1 直播推流流程分析1.1.1 直播推流流程分析1.1.2 推流过程1.1.2 直播rtmp协议分析RTMPRTMP优缺点RTMP协议类型握手推流二、流媒体服务器2.1 直播服务器windows搭建流媒体服务器ubuntu搭建流媒体服务器直接使用Docker搭建好的CDN服务器 前言 一、基础知识 1.1 直播推流流程分析 1.1.1 直播推流流程分析 推流协议:rtmp 直播服务器CDN: nginx-rtmp模块 crtmp-server 客户端播放协议: rtmp

二次元怪兽的博客 6434

系统回溯信息:遇到严重错误。已将回溯写入...PTC... raceback.log请将其发送给|技术支持|。

一、问题分析 这种问题一旦遇上了,如果忘了保存,直接心态爆炸。 该问题一般是显卡驱动的问题。ProE/Creo对显卡的基本要求是:支持Open_GL的3D显卡,分辨率为1024×768像素以上,推荐使用64位独立显卡,显存512MB以上。如若显卡性能太低,打开软件后易自动退出。 二、解决方法 <1>有一个非常简单的方法是:在config.pro文件中,改变图形配置graphics为” win32_gdi”而不是默认的“...

幕光倾城的博客 2万+

java调用第三方api接口获取数据

弊端,但是并不是每个接口都适用于这套模板,之前写的接口不适用,都是在这个模板里做了定制处理(if 特殊表 -》处理),需要考虑兼容之前的代码,这种兼容代码,有风险会改掉之前的逻辑个人喜欢,有一个接口,写一段代码灵活自由些。2.还有一部分是对post请求的返回数据,做处理,因为json返回的格式如果双方没有约定好,里面数据是多层json嵌套,需要做处理。江同事交接的就是这个代码模板,可以看下源码调试一下,之前交接的视频,也可以看下,主要还是看代码理解快些,将 写好的代码达成jar包,上传到linux上去,

随风不随水的博客 2036

Qt与FFmpeg实现跨平台移动视频采集与推流方案

视频采集与传输是多媒体开发中的核心技术,涉及摄像头数据采集、视频编码压缩和网络传输等关键环节。FFmpeg作为业界领先的音视频处理库,提供了强大的编码和协议支持能力,而Qt框架则以其卓越的跨平台特性著称。将两者结合可以构建高效的移动端视频处理方案,支持RTMP/RTSP等主流流媒体协议。这种技术组合特别适合智能家居监控、移动直播等需要低延迟、高可靠性的场景,通过硬件加速和参数优化可进一步提升性能表现。

weixin_29290947的博客 260

航空大数据——项目资源汇总及开源(四)

ADS-B项目开源大礼包

cyc头发还挺多的博客 4983

QT-FFmpeg-系列-5

1、点击跳转 ===> FFmpeg 官方文档地址2、点击跳转 ===> FFmpeg 官方 Wiki 地址3、点击跳转 ===> FFmpeg 翻译文档地址https://xdsnet.gitbooks.io/other-doc-cn-ffmpeg/content/index.html4、点击跳转 ===> FFmpeg 中文论坛地址5、点击跳转 ===> FFmpeg 雷神博客地址6、点击跳转 ===> 罗索实验室官方地址7、点击跳转 ===> FFmpeg 命令行实践+理论分析(视频教程)视频中的地址

Asincereman的专栏 1436

AFSim2.9.0学习笔记 —— 1、AFSim及完整工具介绍(文末附:完整afsim2.9.0源码、编译好的完整工具包、中文教材等)

AFSim2.9.0学习笔记 —— 1、AFSim及完整工具介绍(附:完整afsim2.9.0源码、编译好的完整工具包、中文教材等)

5175

SAP FICO 内部往来对账功能开发说明书(包括测试样例、程序代码仅作参考,不保证一定可以运行)

SAP FICO 内部往来对账功能开发说明书(包括开发源代码、测试样例、FS)

我好像病了的博客 1223

Qt之FFmpeg播放器设计(十七)

​Qt进行播放器设计,采用自带多媒体模块QMediaPlayer设计时,其底层操作系统提供的解码器有些格式不一定支持,且解码效果有限。这时我们一般采用第三方音视频解码器库进行操作,这里推荐FFmpeg库,用的人比较多采用纯C编写,保证高可移植性和编解码质量,且支持多种音视频格式,提供了录制、转换以及流化音视频的完整解决方案。在Qt平台上开发涉及FFmpeg的应用程序,需要将FFmpeg库集成到你的Qt项目中,利用FFmpeg功能进行音视频处理,本文基于Qt平台,依靠FFmpeg内核库,设计了一款播放器。

weixin_47006346的博客 2072

【SI案例分享】1000 BASE-T1共模终端的分析与评估

以太网的MDI接口应用中,共模端接网络的应用广泛,并且,其电路参数从100 BASE-T1到1000 BASE-T1保持了相同的参数定义,这就不得不引起使用者的怀疑,毕竟百兆和千兆以太网的工作频段差异巨大,这样的设计真的可以做到上下兼容吗?本文针对此问题,进行了深入的探讨。

一只豌豆象的博客 1364

Qt+FFmpeg实战:5分钟搞定本地摄像头推流到B站直播(附完整代码)

本文详细介绍了如何利用Qt和FFmpeg快速构建一个本地摄像头推流工具,实现将摄像头画面实时推送到B站直播。文章提供了从环境配置、核心代码到参数调优的完整实战指南,重点解析了摄像头采集、H.264编码及RTMP推流等关键步骤,帮助开发者快速掌握音视频处理与直播推流技术。

ujm5678901的博客 126

倍福--手动加入第三方伺服的配置

客户在没有硬件情况下,希望手动配置硬件,添加第三方驱动器,本文介绍其操作流程 目 录 软硬件版本 3 1.1. 倍福Beckhoff 3 1.1.1. 控制器硬件 3 1.1.2. 控制软件 3 操作步骤 3 2.1. 添加电机XML文件 3 2.2. 创建EtherCAT 3 2.3. 添加新建项 3 2.4. 选择型号 4 2.5. 关联NC轴 5 软硬件版本 1.1. 倍福Beckhoff 1.1.1. 控制器硬件 TwinCAT控制制器,PC或者EPC,包括: 嵌入式控制器:CX5020-

weixin_41883890的博客 5325

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

FFmpeg QT 实现h264、h265 音视频播放(Native方式)

前言 FFmpeg 3.0 以后统一了音视频的编码方式,原有的API已经标记为弃用 或者已经被删除 测试环境: FFmpeg 4.0 Qt 5.12 Ubuntu 18.04 音视频格式 封装格式 常见的视频格式有AVI、MPEG、QuickTime、Flash、Mpeg-4、MKV、WebM,不同的格式有不同的特点 AVI: AVI视频格式,文件名以“.avi”结尾。AVI是英文Audio Video Interleave的缩写,该格式由微软开发。在所有Windows系统都能运行这种格式 MPEG

雨中人的专栏 6147

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

上一篇: FFmpeg线程类封装+QT5实现直播拉流(文末含源代码)
下一篇: Qt窗口中嵌入UE程序(文末含源代码)
taciturn丶
博客等级 码龄9年 471粉丝 33原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

taciturn丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值