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
//设置缓存大小,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输入流
//打开视频流
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. 视频流解码器
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 = 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. 子线程循环对帧数据处理
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. 构造函数
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
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. 启动/停止视频
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、OpenCV和FFmpeg实现实时摄像头数据的推流,并在Web端进行显示

通过使用Qt、OpenCV和FFmpeg,我们可以实现实时摄像头数据的推流,并在Web端进行显示。在这篇文章中,我们介绍了如何初始化摄像头、推流到Web端并在Web端显示视频流的步骤,并提供了相应的代码示例。当接收到数据时,我们将其设置为Video元素的src,从而显示视频流。在上述代码中,我们首先初始化了FFmpeg库,并创建了一个AVFormatContext对象,用于存储输出的格式和流信息。在本文中,我们将探讨如何使用Qt、OpenCV和FFmpeg来实现实时摄像头数据的推流,并在Web端进行显示。

CodeJolt的博客 1006

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

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

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

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

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

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的路上... 1787

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

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

2990

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

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

ever__ever的博客 1556

基于 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实现RTSP视频流播放器:从拉流解码到画面渲染

在实时音视频传输领域,RTSP作为应用广泛的流媒体控制协议,常与H.264等编码标准配合使用,承载着从网络摄像头到客户端之间的连续图像数据。其底层基于RTP/RTCP传输,对延迟和稳定性要求极高,这促使开发者选择具备跨平台解码能力的FFmpeg作为核心引擎。FFmpeg提供了从协议解析、解封装到硬解码的完整链路,配合Qt的线程与事件机制,可以构建低延迟、可定制的视频流播放器。该方案在安防监控、工业视觉、车载终端等场景中具有显著价值,能灵活控制缓冲、实现自动重连和OSD叠加。本文正是围绕这一技术组合,讲解如何

weixin_30225755的博客 238

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

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

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

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

weixin_30041685的博客 1858

基于qt+ffmpeg设计的推流软件(支持rtsp.rtmp)(推流桌面与摄像头)

这是Qt设计的推流软件,核心全部由ffmpeg实现,QT只是做了个界面没有做其他重要的事情。如果要利用ffmpeg开发推流软件,这个一份必看的好东西。目前实现了桌面捕获、摄像头捕获推流,全部由ffmpeg的API实现。

[笔记]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

二次元怪兽的博客 6435

从入门到精通:激光器恒流驱动电路的设计与选型指南

本文深入解析了激光器恒流驱动电路的设计与选型。文章从基础电路原理讲起,对比了简易恒流源与高精度运放反馈方案的优劣,并重点阐述了如何为不同功率与精度要求的激光器项目选择合适的恒流驱动方案,强调了恒流源对于保障激光器稳定工作、防止热失控及延长寿命的核心作用。

grafana6viz的博客 711

微信DAT图片恢复全攻略:从文件定位到批量解密(附最新路径变化)

本文提供了一份详尽的微信DAT图片恢复指南,涵盖新旧版本文件路径变化解析、DAT文件定位方法以及批量解密操作步骤。针对微信图片加密存储为DAT格式的痛点,文章介绍了高效易用的图形化解密工具,帮助用户将加密文件快速还原为JPG、PNG等可查看格式,并强调了数据安全与隐私保护的重要性。

p4q5r6s7t的博客 744

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

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

weixin_47006346的博客 2073

基于32nm CMOS工艺的传输门D触发器设计及HSPICE仿真分析

根据半导体集成电路、利用Hspice软件以及数字电路等课程的知识,使用集成电路CMOS工艺完成触发器的设计,帮助初学者熟悉和掌握集成电路芯片电路设计及模拟方法和技巧

2302_81301620的博客 2452

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

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

weixin_29290947的博客 260

✨使用 vLLM + Docker 部署 Qwen3-Embedding-8B 并实现高效文本向量生成(附 `curl` 测试)

本文介绍了如何使用 vLLM 和 Docker 部署 Qwen3-Embedding-8B 嵌入模型服务,并通过标准 OpenAI 兼容接口进行调用和结果解析。如果你在部署中遇到任何问题,欢迎留言讨论!

用大模型重构生活:猫奴程序员的AI智能日常记录 6239

手把手式介绍 ADNI 影像数据下载

1.搜索 1.1登录 打开ADNI的官网:http://adni.loni.usc.edu/,进入下面的界面。 点击 DATA&SMPLES 下的 ACCESS DATA AND SAMPLES,如下图。 页面跳转以后点击中间的“LOGIN”,这时会跳出一个新的界面,然后在右上角输入自己的帐号、密码即可。如果没有权限可以跳转到第2部分看看如何申请权限。 登录以后选择DOWNLOAD下的 Image Collections就是搜索下载影像数据,其他还有一些学习测试数据和遗传数据。 1.2 sea

永远想不起来更新的博客 1万+

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

详解Linux下使用vscode编译运行和调试C/C++

本教程将具体详解使用Visual Studio Code在Linux环境下使用C/C++方法。 GCC C++ 编译器 (g++) 和 GDB 调试器的方法。 GCC是GNU 编译器集合; GDB 是 GNU 调试器。 1.先决条件 安装Visual Studio Code. 安装C++拓展插件。点击在vscode界面最左侧的Extensions图标(打开快捷键:ctrl+shift+X),搜索“C/C++”,点击进行安装。 确保已安装gcc. 一般ubuntu系统会预装gcc.在终端窗口中输入如下命令

休的博客 6241

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

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

ujm5678901的博客 126

全球气候模式降尺度方法总结及其代码实现

简介总结各类型气候模式降尺度方法

WW、forever的博客 1万+

久坐不慌,健康护航 —— 智能震动提醒坐垫的设计与实现

硬件核心:选用 ESP32 为主控,HX711 + 应变片实现精准压力检测,8050 三极管驱动震动马达,锂电池 + TP4056 满足便携供电需求;软件逻辑:核心是 “压力阈值判断 + 时间计时”,达到久坐阈值后触发震动提醒,流程图清晰体现了循环检测、状态重置的核心逻辑;代码特性:代码注释完善,阈值可灵活调整,支持串口调试,新手可直接编译烧录并根据实际场景校准。

weixin_42300449的博客 401
上一篇: FFmpeg接口使用说明
下一篇: FFmpeg线程类封装+QT5实现直播拉流(含源代码)
taciturn丶
博客等级 码龄9年 471粉丝 33原创
评论 2
成就一亿技术人!
拼手气红包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、付费专栏及课程。

余额充值