QT—-Ffmpeg+SDL播放器开发(停滞)

github地址提交记录与章节对应方便查看代码变化

1、FFmpeg6.0 和SDLwindows安装

1.1 下载ffmpeg

官网下载:https://ffmpeg.org/download.html

file

下载这个版本,gpl-shared,6.0-22,下载完成后解压文件
file

现在代码文件,方便后边使用https://github.com/FFmpeg/FFmpeg/tree/release/6.0

1.2 拷贝文件

将可执行文件拷贝到C:\Windows

file

dll文件拷贝到C:\Windows\SysWOW64(WoW64 (Windows On Windows64 )是一个Windows操作系统的子系统,被设计用来处理许多在32-bit Windows和64-bit Windows之间的不同的问题,使得可以在64-bit Windows中运行32-bit程序。)
file

1.3 添加环境变量

在用户变量path里,添加上解压的ffmpeg的bin文件夹路径,确定

file

1.4 验证安装

重启电脑,Win+R输入cmd,输入ffmpeg -version,成功显示安装成功

file

1.5 SDL2安装

下载SDL2-2.0.10 下载地址 ,解压,把dll文件放入C:\Windows\SysWOW64

file

2 qt测试ffmpeg调用

创建一个纯c的工程,吧ffmpeg代码放到项目文件夹下

file

选择MinGW64版本
file

在.pro文件里添加上ffmpeg的路径和库文件

win32{
INCLUDEPATH += E:\Environment\ffmpeg-6.0\include\

LIBS += E:\Environment\ffmpeg-6.0\lib\avformat.lib \
        E:\Environment\ffmpeg-6.0\lib\avcodec.lib \
        E:\Environment\ffmpeg-6.0\lib\avdevice.lib \
        E:\Environment\ffmpeg-6.0\lib\avfilter.lib \
        E:\Environment\ffmpeg-6.0\lib\avutil.lib \
        E:\Environment\ffmpeg-6.0\lib\postproc.lib \
        E:\Environment\ffmpeg-6.0\lib\swresample.lib \
        E:\Environment\ffmpeg-6.0\lib\swscale.lib
}

引入头文件,添加上测试代码

#include <stdio.h>
#include "libavutil/avutil.h"

int main()
{
    printf("Hello World! ffmpeg verson %s\n",av_version_info());
    return 0;
}

测试成功,若是报错出现.dll确实,可以重启一下电脑,可能是环境变量的路径还没有识别

file

3 原理讲解(没学过这些东西,请教gpt老师)

file

这张图片描述了一个视频文件(time.mp4)的解码和播放流程。这是一个多线程的处理流程,通常用于媒体播放器软件中。这里的处理过程包括分离器(Demuxer)、解码器(Decoder)以及音视频同步(SDL,Simple DirectMedia Layer)的输出。下面是每个部分的解释:
(1)time.mp4:这是一个mp4视频文件,它包含视频和音频数据流。文件路径和基本信息显示在图中的左下角。
(2)mp4demuxer:这是一个解复用线程(DemuxThread),它从mp4文件中提取音视频数据。mp4demuxer负责从容器格式中分离出视频和音频流。
(3)AVPacketQueue:音频和视频数据在解复用后会被放入不同的队列中。视频数据是H264编码的,而音频数据是AAC编码的。这些队列存储数据包,等待被解码。
(4)DecodeThread:解码线程从队列中取出数据包,将它们解码成帧数据。有两个解码线程,一个用于音频,一个用于视频。解码后,视频数据包通常变成YUV格式的帧,音频数据包则变为PCM格式的帧。
(5)AVFrameQueue:解码后的帧会被放入另外一个队列,等待进一步的处理。视频帧队列和音频帧队列分别存储不同类型的数据。
(6)SDL:SDL是一个库,用于处理多媒体的输出。在这个流程中,SDL将被用来输出音频和视频。对于视频,YUV格式的帧数据将被SDL处理并输出到屏幕上。对于音频,PCM格式的帧数据将被SDL处理并通过扬声器播放。
整个流程显示了从一个视频文件开始,如何通过不同的处理步骤来最终在设备上播放音频和视频。这是一个典型的多线程应用,它允许音频和视频的解码同时进行,以保持播放的同步。在实际的软件实现中,还需要考虑到缓冲、错误处理、音视频同步等因素。

4 先实现ffmpeg播放视频

创建项目,MinGW64,对于c++,头文件的引入需要修改,执行看到可以运行

win32{
INCLUDEPATH += E:\Environment\ffmpeg-6.0\include\

LIBS += E:\Environment\ffmpeg-6.0\lib\avformat.lib \
        E:\Environment\ffmpeg-6.0\lib\avcodec.lib \
        E:\Environment\ffmpeg-6.0\lib\avdevice.lib \
        E:\Environment\ffmpeg-6.0\lib\avfilter.lib \
        E:\Environment\ffmpeg-6.0\lib\avutil.lib \
        E:\Environment\ffmpeg-6.0\lib\postproc.lib \
        E:\Environment\ffmpeg-6.0\lib\swresample.lib \
        E:\Environment\ffmpeg-6.0\lib\swscale.lib
}
//需要这么引入
#ifndef __STDC_CONSTANT_MACROS
#define __STDC_CONSTANT_MACROS
#endif
#ifdef __cplusplus ///
extern "C"
{
#include "libavutil/avutil.h"
}
#endif

file

4.1 解复用实现

分配解码器上下文(avformat_alloc_context):初始化一个用于存储编解码器相关信息的结构体。
打开视频文件(avformat_open_input):通过给定的URL或文件路径打开视频文件。
获取流信息(avformat_find_stream_info):读取媒体文件中的流信息,如音频、视频等流的编码信息,会给音频和视频各奉陪一个需要我们要记录。
读取和定位流数据(在一个循环内部进行):
读取帧(av_read_frame):读取媒体流的下一帧数据。
定位操作(avformat_seek_file 和 av_seek_frame):在媒体文件中定位,可以快进、倒退到特定的帧。
关闭并释放资源(avformat_close_input):完成视频处理后,关闭文件并释放资源。

file

添加一个Thread的c++文件
file

实现了简单的线程管理类

class Thread
{
public:
    Thread(); // 构造函数
    ~Thread(); // 析构函数
    int Start(); // 启动线程
    int Stop(); // 停止线程
    void test_main();
    virtual void Run() = 0;
protected:
    int abort_ = 0; // 标记线程是否应该停止
    std::thread *thread_ = nullptr; // 线程对象指针,默认为 nullptr
};

Thread::~Thread()
{
    if(thread_)
    {
        Thread::Stop(); // 在析构函数中停止线程
    }
}

int Thread::Start()
{
    // 在此处添加启动线程的代码
    // 返回适当的值以指示启动是否成功
}

int Thread::Stop()
{
    abort_ = 1; // 设置停止标记为真
    if(thread_)
    {
        thread_->join(); // 等待线程结束
        delete thread_; // 删除线程对象
        thread_ = nullptr; // 将线程指针设置为 nullptr
    }
    // 返回适当的值以指示停止是否成功
}

void Thread::test_main()
{
    qDebug()<<"Hello";
}

添加c++文件DemuxThread,用来解复用

file

在thread。cpp里添加上,方便测试

void Thread::test_main()
{
    qDebug()<<"Hello test main";

    //1、解复用
    DemuxThread demux_thread;
    demux_thread.Init("test.mp4");
}

头文件代码,后边可以先传一个url来测试是否调用了析构和构造

#ifndef DEMUXTHREAD_H
#define DEMUXTHREAD_H
#include "thread.h"
#include <QDebug>
#ifndef __STDC_CONSTANT_MACROS
#define __STDC_CONSTANT_MACROS
#endif
#ifdef __cplusplus ///
extern "C"
{
#include "libavutil/avutil.h"
#include "libavformat/avformat.h"
}
#endif

class DemuxThread
{
public:
    DemuxThread(); // 构造函数
    void Init(const char *url); // 初始化方法,使用给定的 URL
    int Start(); // 开始解复用方法
    int Stop(); // 停止解复用方法
    void Run(); // 运行解复用过程的方法

private:
    std::string url_; // 要解复用的文件名或 URL
    AVFormatContext *ifmt_ctx_ = NULL; // 用于解复用的 AVFormatContext 结构体指针
};

#endif // DEMUXTHREAD_H

我们在init函数中获取视频和音频流,以及视频的相关信息。把一个有视频和声音的视频重命名放到编译的路径下,运行代码我们就能看到相关信息。

int DemuxThread::Init(const char *url)
{
    qDebug() << "初始化:"<< url; // 打印初始化的URL
    int ret = 0;
    url_ = url;

    ifmt_ctx_ = avformat_alloc_context(); // 分配 AVFormatContext 结构体内存

    ret = avformat_open_input(&ifmt_ctx_, url_.c_str(), NULL, NULL); // 打开输入流

    if (ret != 0)
    {
        char errbuf[AV_ERROR_MAX_STRING_SIZE]; // 用于存储错误信息的缓冲区
        av_strerror(ret, errbuf, sizeof(errbuf)); // 将错误码转换为错误消息字符串
        qDebug() << "打开输入时发生错误:" <<  errbuf; // 输出错误信息
        return -1; // 返回错误码表示初始化失败
    }

    ret = avformat_find_stream_info(ifmt_ctx_, NULL); // 查找流信息
    if (ret != 0)
    {
        char errbuf[AV_ERROR_MAX_STRING_SIZE]; // 用于存储错误信息的缓冲区
        av_strerror(ret, errbuf, sizeof(errbuf)); // 将错误码转换为错误消息字符串
        qDebug() << "查找流信息时发生错误:" <<  errbuf; // 输出错误信息
        return -1; // 返回错误码表示初始化失败
    }
    av_dump_format(ifmt_ctx_, 0, url_.c_str(), 0); // 打印输入格式信息

    audio_index_ = av_find_best_stream(ifmt_ctx_, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0); // 查找最佳音频流索引
    video_index_ = av_find_best_stream(ifmt_ctx_, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); // 查找最佳视频流索引
    qDebug() << QString("音频流索引:%1,视频流索引:%2").arg(audio_index_).arg(video_index_);
    if (audio_index_ < 0 || video_index_ < 0)
    {
        return -1; // 如果找不到音频或视频流,返回错误码表示初始化失败
    }
}

file

接着编写run函数,他需要读取abort_,这是Thread类的保护变量,所以需要继承class DemuxThread : public Thread,这样我们就可以访问abort_变量。Run函数是主要的解复用循环,它不断地从输入流中读取数据包,直到收到终止信号。

void DemuxThread::Run()
{
    int ret = 0;
    AVPacket pkt;

    // 循环读取数据包直到终止信号被设置
    while (abort_ != 1)
    {
        // 读取一个数据包
        ret = av_read_frame(ifmt_ctx_, &pkt);
        if (ret < 0)
        {
            char errbuf[AV_ERROR_MAX_STRING_SIZE]; // 用于存储错误信息的缓冲区
            av_strerror(ret, errbuf, sizeof(errbuf)); // 将错误码转换为错误消息字符串
            qDebug() << "读取数据包时发生错误:" <<  errbuf; // 输出错误信息
            break; // 如果读取失败,退出循环
        }
    }
}

Start 方法创建了一个新线程来运行 Run 方法,如果创建线程失败则返回错误码;Stop 方法调用了基类 Thread 的停止方法,并关闭了输入流

int DemuxThread::Start()
{
    // 创建新线程来运行 Run 方法
    thread_ = new std::thread(&DemuxThread::Run, this);
    if (!thread_)
    {
        qDebug() << "创建线程失败";
        return -1; // 返回错误码表示启动失败
    }
    return 0; // 返回成功码表示启动成功
}

int DemuxThread::Stop()
{
    Thread::Stop(); // 调用基类 Thread 的停止方法
    avformat_close_input(&ifmt_ctx_); // 关闭输入流
}

再到testmain里添加运行代码,启动程序,发现能够读取视频流和音频流

int Thread::test_main()
{
    qDebug() << "测试主函数开始";
    int ret = 0;
    //1、解复用
    DemuxThread demux_thread;
    ret = demux_thread.Init("test.mp4");
    if (ret < 0)
    {
        qDebug() << "初始化失败";
        return -1;
    }

    ret = demux_thread.Start();
    if (ret < 0)
    {
        qDebug() << "启动失败";
        return -1;
    }
    std::this_thread::sleep_for(std::chrono::milliseconds(10000)); // 休眠10秒
    demux_thread.Stop();
}

file

如果觉得本文对您有所帮助,可以支持下博主,—分也是缘。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇

超多性价比流量卡,扫码查看

这将关闭于 20