QT ソフトウェア開発 - FFMPEG に基づくビデオ プレーヤーの設計 - ソフト ソリューション イメージ (1)

QT ソフトウェア開発 - FFMPEG に基づくビデオ プレーヤーの設計 - CPU ソフト ソリューション ビデオ (1)
https://xiaolong.blog.csdn.net/article/details/126832537

QT ソフトウェア開発 - FFMPEG に基づくビデオ プレーヤーの設計 - GPU ハード ソリューション ビデオ (2)
https://xiaolong.blog.csdn.net/article/details/126833434

QT ソフトウェア開発 - FFMPEG に基づくビデオ プレーヤーの設計 - オーディオのデコード (3)
https://xiaolong.blog.csdn.net/article/details/126836582

QT ソフトウェア開発 - FFMPEG に基づくビデオ プレーヤーの設計 - OpenGL レンダリング ビデオ (4)
https://xiaolong.blog.csdn.net/article/details/126842988

QT ソフトウェア開発 - FFMPEG に基づくビデオ プレーヤーの設計 - ストリーミング メディア プレーヤー (5)
https://xiaolong.blog.csdn.net/article/details/126915003

QT ソフトウェア開発 - FFMPEG に基づくビデオ プレーヤーの設計 - ビデオ プレーヤー (6)
https://blog.csdn.net/xiaolong1126626497/article/details/126916817

I.はじめに

ffmpegといえば、オーディオやビデオ関連の開発をしている方なら一度は耳にしたことがあるはずです。FFmpeg は、非常に高度なオーディオ/ビデオ コーデック ライブラリを提供し、クロスプラットフォームをサポートします。

現在、インターネット上には ffmpeg 関連の記事やチュートリアルが数多くありますが、ffmpeg 自体は、主にビデオとオーディオのデコードとエンコードに使用されます。画像のレンダリング、サウンド出力、およびその他の機能は提供しません。独自のプレーヤーを設計するには、最初に、タスクを完了するために ffmpeg の呼び出しに協力するための他の知識が必要です。

ffmpeg の使用法を簡単かつ迅速に紹介するために、いくつかの記事を読み、いくつかのケースを書いて ffmpeg の使用法を段階的に説明し、最終的に理論的な知識を必要としない完全なプレーヤー開発を完了します (理論的な知識が多すぎます)。インターネット上)、主にコードと機能に基づいています。

ここでビデオ プレーヤーの開発に使用した環境の紹介:

ffmpeg版本:  4.2.2
Qt版本    :  5.12.6
编译器类型 : MinGW32bit 

これらの記事の段階的なコンテンツ計画とケースは次のとおりです。

(1) ffmpeg を使用してビデオをデコードし、QWidget を介してデコードされた画像をレンダリングし、プログレス バー ジャンプ、プログレス バー表示、合計時間表示、および基本的なビデオ情報表示をサポートします。

機能: ソフトウェア デコード (CPU) を使用して、画像データのみをデコードし、音声データを無視して、主に ffmpeg の基本的な使用プロセス、ffmpeg を介してビデオ デコードを完了する方法、画像ピクセル フォーマットを変換し、最後に画像レンダリングを完了する方法を示します。

(2) ffmpeg のハードウェア アクセラレーション インターフェイスを使用してビデオ デコードを完了し、現在のハードウェアでサポートされているアクセラレーション メソッドの検出をサポートし、デコード後に GPU から CPU にデータをコピーし、ピクセル変換を完了し、QWidget を介して画像をレンダリングし、プログレス バー ジャンプ、プログレス バーをサポートします。表示、総時間表示、動画基本情報表示。

機能: ハードウェア アクセラレータによるデコード (GPU) を使用して、画像データのみをデコードし、音声データを無視して、主に ffmpeg ハードウェア デコードの基本的な使用プロセス、ffmpeg を介してビデオ デコードを完了する方法、画像ピクセル フォーマットを変換する方法、および最後に画像レンダリングを完了する方法を示します。

(3) ffmpeg を使用してビデオのオーディオ パケットをデコードし、QAudioOutput を介してオーディオ データを再生します。

機能: オーディオ データのみをデコードし、ビデオ イメージ データを無視する. 主に、ffmpeg の基本的な使用プロセス、ffmpeg を介してオーディオ データのデコードを完了する方法、オーディオ データ形式を変換する方法、および最後に QAudioOutput を介して再生する方法を示します。

(4) ffmpeg のハードウェア アクセラレーション インターフェイスを使用して、ビデオ デコードを完了し、現在のハードウェアでサポートされているアクセラレーション メソッドの検出をサポートし、デコードされたQOpenGLWidget画像データをレンダリングすることで、プログレス バー ジャンプ、プログレス バー表示、合計時間表示、ビデオ基本情報表示をサポートします。

機能: ハードウェア アクセラレータによるデコード (GPU)、OpenGL レンダリング、画像データのみのデコード、音声データは無視、主に ffmpeg ハードウェア デコードと OpenGL レンダリングの基本的な使用プロセスを示します。

(5) (4) の例にストリーミング メディアの再生サポートを追加し、rtmp、rtsp、HLS (HTTP プロトコル) などの一般的なストリーミング メディア フォーマットをサポートし、ffmpeg のハードウェア アクセラレーション インターフェイスを使用してビデオのデコードを完了し、現在のハードウェア サポートの検出をサポートします。デコードされた画像データをレンダリングする高速化された方法QOpenGLWidget

機能: ハードウェア アクセラレータによるデコード (GPU)、OpenGL レンダリング、画像データのみのデコード、音声データは無視、主に ffmpeg ハードウェア デコードと OpenGL レンダリングの基本的な使用プロセスを示します。

(6) (3) と (5) の例を組み合わせ、オーディオ パケットのデコードと再生を追加し、ffmpeg のハードウェア アクセラレーション インターフェイスを使用してビデオ デコードを完了し、現在のハードウェアでサポートされているアクセラレーション メソッドの検出をサポートし、デコードされた画像データをレンダリングします。QOpenGLWidgetQAudioOutput を介してオーディオ データを再生します。プログレスバージャンプ、プログレスバー表示、トータルタイム表示、動画基本情報表示に対応。

機能: ハードウェア アクセラレータによるデコード (GPU) の使用、OpenGL レンダリング、画像データのみのデコード、オーディオ データの無視、主に ffmpeg ハードウェア デコードと OpenGL レンダリングの基本的な使用プロセス、および QAudioOutput を介したオーディオ データの再生プロセスを示します。

(7) 前の例と組み合わせて、完成したビデオ プレーヤーを設計します。

2. デコードとレンダリング

ビデオプレーヤーを作りたい場合、主に3つの問題を解決します: (1) デコード (2) レンダリング (3) オーディオとビデオの同期

2.1 デコード

ffmpeg は、純粋なソフトウェア デコードとハードウェア アクセラレーションによるデコードをサポートしています。純粋なソフトウェア デコーディングが CPU に依存している限り、高解像度 (4K 以上) のビデオ ソフトウェア デコーディングが多くの CPU を占有し、デコーディング速度が比較的遅く、さらにレンダリング時間がかかる場合、全体的に動画プレイヤーがフリーズする 現象 ハードウェア アクセラレーション (GPU) を使用してデコードすると、デコード時間が大幅に短縮されます。私のコンピュータは i7 低電力 CPU です. 私のコンピュータでテストしました. 解像度が 3840x2160 のビデオの場合, ソフトウェアによるフレームのデコードには約 300 ミリ秒かかります. ハードウェアアクセラレーションによるデコードが有効になっている場合, デコードには約 10 ミリ秒かかります.フレーム. 速度の違いは非常に大きいです. また、ソフト デコード中の CPU 使用率はほぼ 100% です.GPU でデコードすると、CPU の負荷が非常に低くなり、他のことをするためにより多くの時間を割くことができます.

ソフト ソリューションとハード ソリューションの違いはそれほど大きくありません。ffmpeg にはすべての API が既にカプセル化されており、呼び出すだけでよく、最下層で多くのことを理解する必要がないため、非常に簡単です。開発に便利。

ビデオデコードの例を含む、ffmpeg ソースコードの下で提供される多くの例があります。

ffmpeg\doc\examples\decode_video.c
ffmpeg\doc\examples\hw_decode.c

これらの 2 つの例は非常に参考になります. その中には ffmpeg ハードウェア アクセラレーションによるデコードの例hw_decode.c があります. この例を通じて、ffmpeg がハードウェア デコードのために GPU を呼び出す方法を理解できます.

もちろん、ffmpeg はコマンドライン プレーヤーも提供します。ソース コードは ffplay.c です。このコードは非常によく実装されています。単なるプレーヤーですが、ffmpeg 独自の API 呼び出しに加えて、ffplay.c にはさらに多くのコードがあります。レンダリング部分はSDLで実装されているので、初期段階でffmpegやSDLに慣れていない方は、直接ffplay.cに行ってもあまり効果がないかもしれません。

最良の方法は、簡単に始めて、段階を追って理解し、最後に ffplay.c に移動することです。これにより、効果が大幅に向上します。

2.2 レンダリング

ffmpeg 自体はデコードとエンコードのための単なるライブラリであり、デコードされた画像のレンダリングはそれ自体で実装する必要があります。いわゆるレンダリングとは、動画をffmpegでデコードした後の画像データを表示することです。レンダリングは、ソフトウェア レンダリングとハードウェア アクセラレーション レンダリングにも分けられます。現在、私のUIはQtで作っていますが、Qwidgetで直接描画したり、QLabelで表示したりと、Qtで画像を表示する方法はたくさんあります。この方法は最も一般的な方法であり、最も単純な方法です.この方法は、描画にCPUを使用するため、多くのCPUを使用し、Qwidget、QLabelなどを介して表示する必要があり、ffmpegによってデコードされたデータが必要です.ピクセル形式に変換されます。QImage形式への再パッケージ化は、時間のかかるプロセスです。CPU 使用率を下げてレンダリングを高速化したい場合は、Qt にパッケージ化されているレンダリングに OpenGL を使用できますが、QOpenGLWidgetOpenGL を呼び出すと比較的便利です。

3. ビデオ プレーヤーの設計

3.1 設計の説明

ffmpegを使用してビデオをデコードし、QWidgetを介してデコードされた画像をレンダリングし、プログレスバーのジャンプ、プログレスバーの表示、合計時間の表示、およびビデオの基本情報表示をサポートします。

機能: ソフトウェア デコード (CPU) を使用して、画像データのみをデコードし、音声データを無視して、主に ffmpeg の基本的な使用プロセス、ffmpeg を介してビデオ デコードを完了する方法、画像ピクセル フォーマットを変換し、最後に画像レンダリングを完了する方法を示します。

ビデオのデコードは独立したサブスレッドを採用し、デコード後に得られた画像データは、信号スロット伝送を介してレンダリング用の UI インターフェイスに送信されます。

3.2 デコード処理

以下は、ffmpeg ソフトウェア デコードの基本的なフローです。

//1.打开多媒体流,并且获取一些信息
avformat_open_input(&format_ctx, m_MediaFile, nullptr, nullptr)


//2. 读取媒体文件的数据包以获取流信息
avformat_find_stream_info(format_ctx, nullptr)


//3. 查找解码器
AVCodec *video_pCodec=avcodec_find_decoder(stream->codecpar->codec_id);

//4. 打开解码器
avcodec_open2(stream->codec,video_pCodec,nullptr)

//5. 读取一帧数据
av_read_frame(format_ctx, &pkt)

//6. 发送视频帧
avcodec_send_packet(format_ctx->streams[video_stream_index]->codec,&pkt)

//7. 对视频帧进行解码
avcodec_receive_frame(format_ctx->streams[video_stream_index]->codec, SRC_VIDEO_pFrame)

//8. 转换像素格式
sws_scale(img_convert_ctx,
	   (uint8_t const **) SRC_VIDEO_pFrame->data,
	   SRC_VIDEO_pFrame->linesize, 0, video_height, RGB24_pFrame->data,
	   RGB24_pFrame->linesize);

//9. 渲染

ここで時間がかかるのは、sws_scaleビデオの解像度が高いほど時間がかかるということです。2 つ目はavcodec_receive_frame、レンダリングです。ソフト デコードされたビデオが 4K を超える場合、デコードに多くの時間がかかりますが、解像度が 4K 未満の場合、デコードにかかる時間は許容されます。

3.3 運用効果

画像-20220913140928520

4. ソースコード

4.1 thread.cpp のデコード

//指定文件的编码为UTF-8
#pragma execution_character_set("utf-8")

#include "ReverseDecodThread.h"

ReverseDecodThread::ReverseDecodThread()
{
    
    
    qDebug() << "FFMPEG版本信息:" << av_version_info();
}

ReverseDecodThread::~ReverseDecodThread()
{
    
    

}


/*
功能: 设置媒体文件
*/
int ReverseDecodThread::set_VideoFile(QString media)
{
    
    
    //打开媒体文件
    QByteArray array=media.toUtf8();
    strncpy(m_MediaFile, array.data(), sizeof(m_MediaFile));
}


void ReverseDecodThread::SetSate(int run)
{
    
    
	m_run = run;
}

int ReverseDecodThread::GetSate()
{
    
    
	return m_run;
}


//跳转视频帧
void ReverseDecodThread::SetSeekPos(qint64 pos)
{
    
    
	is_CurrentSeekPos = 1;
    m_n64CurrentSeekPos = pos;
    m_run=1;  //运行状态

    //获取系统本地时间
    play_base_time=QDateTime::currentMSecsSinceEpoch();
}


void ReverseDecodThread::PausePlay()
{
    
    
	m_run = 2;
}

void ReverseDecodThread::StopPlay()
{
    
    
	m_run = 0;
}

void ReverseDecodThread::LogSend(QString text)
{
    
    
	qDebug() << text;
}


//线程执行起点
void ReverseDecodThread::run()
{
    
    
    LogSend("开始播放视频.\n");
    StartPlay();
}


//播放视频
int ReverseDecodThread::StartPlay()
{
    
    
    //1.打开多媒体流,并且获取一些信息
    if(avformat_open_input(&format_ctx, m_MediaFile, nullptr, nullptr) != 0)
    {
    
    
         LogSend(tr("无法打开视频文件: %1").arg(m_MediaFile));
         return -1;
    }

    //2. 读取媒体文件的数据包以获取流信息
    if(avformat_find_stream_info(format_ctx, nullptr) < 0)
    {
    
    
        LogSend(tr("无法获取流信息.\n"));
        return -1;
    }

    //3.打印视频的信息
    LogSend(tr("视频中流的数量: %1\n").arg(format_ctx->nb_streams));
    for(int i = 0; i < format_ctx->nb_streams; ++i)
    {
    
    
        const AVStream* stream = format_ctx->streams[i];
        if(stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
    
    
            //查找解码器
            AVCodec *video_pCodec=avcodec_find_decoder(stream->codecpar->codec_id);
            //打开解码器
            if(avcodec_open2(stream->codec,video_pCodec,nullptr)!=0)
            {
    
    
                  LogSend(tr("解码器打开失败.\n"));
                  return -1;
            }
            video_stream_index = i;
            //得到视频帧的宽高
            video_width=stream->codecpar->width;
            video_height=stream->codecpar->height;

            LogSend(tr("视频帧的尺寸(以像素为单位): (宽X高)%1x%2 像素格式: %3\n").arg(
                stream->codecpar->width).arg(stream->codecpar->height).arg(stream->codecpar->format));
        }
    }

    if (video_stream_index == -1)
    {
    
    
         LogSend("没有检测到视频流.\n");
         return -1;
    }

    AVRational frameRate = format_ctx->streams[video_stream_index]->avg_frame_rate;

    /*设置视频转码器*/
    SRC_VIDEO_pFrame = av_frame_alloc();
    RGB24_pFrame = av_frame_alloc();// 存放解码后YUV数据的缓冲区

    //将解码后的YUV数据转换成RGB24
    img_convert_ctx = sws_getContext(video_width, video_height,
            format_ctx->streams[video_stream_index]->codec->pix_fmt,video_width, video_height,
            AV_PIX_FMT_RGB24, SWS_BICUBIC, nullptr, nullptr, nullptr);

    //计算RGB图像所占字节大小
    int numBytes=avpicture_get_size(AV_PIX_FMT_RGB24,video_width,video_height);

    //申请空间存放RGB图像数据
    out_buffer_rgb = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));

    // avpicture_fill函数将ptr指向的数据填充到picture内,但并没有拷贝,只是将picture结构内的data指针指向了ptr的数据
    avpicture_fill((AVPicture *) RGB24_pFrame, out_buffer_rgb, AV_PIX_FMT_RGB24,
            video_width, video_height);

    qDebug()<<"format_ctx->duration:"<<format_ctx->duration;

    //获取系统本地时间
    play_base_time=QDateTime::currentMSecsSinceEpoch();

    //表示视频加载成功
    while(m_run)
    {
    
    
        if(m_run == 2)
        {
    
    
            msleep(100); //暂停播放
			continue;
        }

		if (is_CurrentSeekPos)
		{
    
    
			is_CurrentSeekPos = 0;
            //偏移到指定位置再开始解码    AVSEEK_FLAG_BACKWARD 向后找最近的关键帧
            av_seek_frame(format_ctx, -1, m_n64CurrentSeekPos* AV_TIME_BASE, AVSEEK_FLAG_BACKWARD);
            qDebug()<<"跳转的位置:"<<m_n64CurrentSeekPos;
		}

        double video_clock;
        AVPacket pkt;

        //1. 读取一帧数据
        if(av_read_frame(format_ctx, &pkt) < 0)
        {
    
    
            m_run=2; //设置为暂停状态
            qDebug()<<"数据读取完毕.";
            continue;
        }

        if(pkt.stream_index == video_stream_index)
        {
    
    
            //当前时间
           video_clock = av_q2d(format_ctx->streams[video_stream_index]->time_base) * pkt.pts;

           qDebug()<<"pkt.pts:"<<pkt.pts<<"video_clock:"<<video_clock;


           //解码视频 frame
           //2. 发送视频帧
            if (avcodec_send_packet(format_ctx->streams[video_stream_index]->codec,&pkt) != 0)
            {
    
    
                av_packet_unref(&pkt);//不成功就释放这个pkt
                continue;
            }

            //3. 接受后对视频帧进行解码
            if (avcodec_receive_frame(format_ctx->streams[video_stream_index]->codec, SRC_VIDEO_pFrame) != 0)
            {
    
    
                av_packet_unref(&pkt);//不成功就释放这个pkt
                continue;
            }

            //4. 转格式
           sws_scale(img_convert_ctx,
                   (uint8_t const **) SRC_VIDEO_pFrame->data,
                   SRC_VIDEO_pFrame->linesize, 0, video_height, RGB24_pFrame->data,
                   RGB24_pFrame->linesize);


    
           //5. 加载图片数据
           QImage image(out_buffer_rgb,video_width,video_height,QImage::Format_RGB888);


           //通过pts同步视频帧--显示视频帧
//           while (true)
//           {
    
    
//                qint64 t1=QDateTime::currentMSecsSinceEpoch();
//                qint64 t2=t1-play_base_time;

//                qDebug()<<"t1:"<<t1;
//                qDebug()<<"t2:"<<t2;
//                qDebug()<<"video_clock:"<<video_clock*1000;

//                if(t2>=video_clock*1000)
//                {
    
    
//                    break;
//                }
//                else
//                {
    
    
//                     QThread::msleep(1);
//                }
//           }

           //通知界面更新
           VideoDataOutput(image.copy());

           //时间信号
           sig_getCurrentTime(video_clock, format_ctx->duration *1.0 / AV_TIME_BASE);

          // QThread::msleep(40);
        }
               //释放包
           av_packet_unref(&pkt);

    }

    LogSend("视频音频解码播放器的线程退出成功.\n");

    if(SRC_VIDEO_pFrame) av_frame_free(&SRC_VIDEO_pFrame);
    if(RGB24_pFrame) av_frame_free(&RGB24_pFrame);
    if(img_convert_ctx)sws_freeContext(img_convert_ctx);
    if(out_buffer_rgb)av_free(out_buffer_rgb);

    SRC_VIDEO_pFrame=nullptr;
    RGB24_pFrame=nullptr;
    img_convert_ctx=nullptr;
    out_buffer_rgb=nullptr;

    if(format_ctx)
    {
    
    
        avformat_close_input(&format_ctx);//释放解封装器的空间,以防空间被快速消耗完
        avformat_free_context(format_ctx);
    }

    return 0;
}

4.2 thread.h のデコード

#ifndef VIDEO_PLAY_H
#define VIDEO_PLAY_H

#include <QThread>
#include <qdebug.h>
#include <QImage>
#include <QDateTime>

extern "C" {
    
    
#include <libavutil/opt.h>
#include <libavutil/mem.h>
#include <libavutil/fifo.h>
#include <libavutil/pixfmt.h>
#include <libavutil/log.h>
#include <libavutil/opt.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
#include <libavfilter/avfilter.h>
#include <libavfilter/buffersrc.h>
#include <libavfilter/buffersink.h>
}


//视频音频解码线程
class ReverseDecodThread: public QThread
{
    
    
    Q_OBJECT
public:

    //构造函数
    ReverseDecodThread();
    ~ReverseDecodThread();
	char m_MediaFile[1024];
	int m_run; //1表示运行 0表示停止 2表示暂停
    double m_n64CurrentSeekPos = 0;  //当前seek位置
	bool is_CurrentSeekPos = 0; //1需要跳转 0不需要

	void SetSate(int run);
	int GetSate();
	void SetSeekPos(qint64 pos);
	void PausePlay();
	void StopPlay();
	void LogSend(QString text);

    //加载视频文件
    int set_VideoFile(QString media);

protected:
    void run();
	int StartPlay();
signals:
    void sig_getCurrentTime(double Sec, double total_Sec);
    void VideoDataOutput(QImage); //输出信号
private:
    int video_width=0;
    int video_height=0;
    AVFormatContext *format_ctx=nullptr;
    int video_stream_index = -1;
    AVFrame *RGB24_pFrame = nullptr;
    AVFrame *SRC_VIDEO_pFrame= nullptr;
    uint8_t *out_buffer_rgb= nullptr;
    struct SwsContext *img_convert_ctx=nullptr;  //用于解码后的视频格式转换

    double video_clock_tmp;

    qint64 play_base_time=0;
};
#endif // VIDEO_PLAY_H

4.3 ウィジェットのレンダリング

#include "VideoFrameDisplay.h"

#include <QPainter>

VideoFrameDisplay::VideoFrameDisplay(QWidget *parent) :
    QWidget(parent)
{
    
    
    m_nRotateDegree=0;
    this->setMouseTracking(true);
}

VideoFrameDisplay::~VideoFrameDisplay()
{
    
    

}

void VideoFrameDisplay::Set_Rotate(int Rotate)
{
    
    
    m_nRotateDegree=Rotate;
}

void VideoFrameDisplay::paintEvent(QPaintEvent *event)
{
    
    
    QPainter painter(this);
	
	painter.setRenderHint(QPainter::Antialiasing);
    painter.setRenderHint(QPainter::TextAntialiasing);
    painter.setRenderHint(QPainter::SmoothPixmapTransform);
    painter.setRenderHint(QPainter::HighQualityAntialiasing);
	
    painter.setBrush(Qt::black);
    painter.drawRect(0,0,this->width(),this->height()); //先画成黑色

    if (mImage.size().width() <= 0) return;

    //将图像按比例缩放成和窗口一样大小
    QImage img = mImage.scaled(this->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); 

    //画面旋转
    if(m_nRotateDegree > 0)
    {
    
    
        QTransform matrix;
        matrix.rotate(m_nRotateDegree);
        img = img.transformed(matrix, Qt::SmoothTransformation);
    }

    int x = this->width() - img.width();
    int y = this->height() - img.height();

    x /= 2;
    y /= 2;

    painter.drawImage(QPoint(x,y),img); //画出图像
}


void VideoFrameDisplay::slotSetOneFrame(QImage img)
{
    
    
    src_mImage =mImage = img;
    update(); //调用update将执行 paintEvent函数
}


/*
功能: 获取原图数据
*/
QImage VideoFrameDisplay::GetImage()
{
    
    
    return src_mImage.copy();
}

/*
功能: 鼠标双击事件
*/
void VideoFrameDisplay::mouseDoubleClickEvent(QMouseEvent *e)
{
    
    
    emit s_VideoWidgetEvent(1);
}

4.4 UI メインスレッド widget.cpp

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    
    
    ui->setupUi(this);

    ui->horizontalSlider_time->installEventFilter(this);

    //关联视频解码器
    connect(&DecodeWorkThread, SIGNAL(VideoDataOutput(QImage)), ui->widget_video, SLOT(slotSetOneFrame(QImage)));

    //当前时间
    connect(&DecodeWorkThread, SIGNAL(sig_getCurrentTime(double, double)), this, SLOT(slotGetCurrentTime(double, double)));
}


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


void Widget::slotGetCurrentTime(double pts, double duration)
{
    
    
    ui->horizontalSlider_time->setMaximum(duration);
    ui->horizontalSlider_time->setMinimum(0);
    ui->horizontalSlider_time->setValue(pts);

    ui->label_duration->setText(QString("%1/%2").arg(pts).arg(duration));
}


void Widget::on_pushButton_play_clicked()
{
    
    
    DecodeWorkThread.SetSate(0);
    DecodeWorkThread.quit();
    DecodeWorkThread.wait();

    DecodeWorkThread.SetSate(1);

    QString filename = QFileDialog::getOpenFileName(this, "选择打开的文件", "C:/", tr("*.*"));
    DecodeWorkThread.set_VideoFile(filename);
    DecodeWorkThread.start();
}


void Widget::on_pushButton_pause_clicked()
{
    
    
    if (DecodeWorkThread.GetSate() == 2)
    {
    
    
        DecodeWorkThread.SetSate(1);
    }
    else if(DecodeWorkThread.GetSate() == 1)
    {
    
    
        DecodeWorkThread.SetSate(2);
    }
}


void Widget::on_pushButton_stop_clicked()
{
    
    
     DecodeWorkThread.SetSate(0);
}


bool Widget::eventFilter(QObject *obj, QEvent *event)
{
    
    
    //解决QSlider点击不能到鼠标指定位置的问题
    if(obj==ui->horizontalSlider_time)
    {
    
    
        if (event->type()==QEvent::MouseButtonPress) //判断类型
        {
    
    
            QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
            if (mouseEvent->button() == Qt::LeftButton)	//判断左键
            {
    
    
               int value = QStyle::sliderValueFromPosition(ui->horizontalSlider_time->minimum(), ui->horizontalSlider_time->maximum(), mouseEvent->pos().x(), ui->horizontalSlider_time->width());
               ui->horizontalSlider_time->setValue(value);

               qDebug()<<"value:"<<value;
               //跳转到指定位置
               DecodeWorkThread.SetSeekPos(ui->horizontalSlider_time->value());
            }
        }
    }
    return QObject::eventFilter(obj,event);
}

4.5 pro ファイル (ffmpeg ライブラリをロード)

win32
{
    
    
    message('运行win32版本')
    INCLUDEPATH+=C:/FFMPEG/ffmpeg_x86_4.2.2/include
    LIBS+=C:/FFMPEG/ffmpeg_x86_4.2.2/bin/av*
    LIBS+=C:/FFMPEG/ffmpeg_x86_4.2.2/bin/sw*
    LIBS+=C:/FFMPEG/ffmpeg_x86_4.2.2/bin/pos*
}

おすすめ

転載: blog.csdn.net/xiaolong1126626497/article/details/126832537