(三) FFmpeg结合Qt实现视频播放器(一)

qt+ffmpeg视频播放器实现音视频倍速功能 参考了云天之巅的FFMPEG Qt视频播放器源码,该代码实现了ffmpeg qt音视频播放器功能,视频音频的播放、暂停、进度调节功能都齐全,非常值得参考。该播放器没有音视频倍速功能,我在网上找了很多ffmpeg音视频倍速功能资料,视频倍速还比较容易实现,但是音频倍速的资料太少了,费了一番功夫基本实现了ffmpeg的音频倍速功能。 阅读详情

目录

前言

(二) FFmpeg解码视频学习已经完成了Qt实现视频播放器的一部分,但是没有音频,而且在播放大的视频的时候,甚至会到导致我的Ubuntu卡死。继续跟着大神们学习!

SDL的使用

SDL概述

主流的音频开源库:OpenAL、PortAudio、SDL、SDL_audioin。其中SDL资料多,学习方便;跨平台;库体积相对比较小。但是SDL**不能采集音频**,FFmpeg支持音频采集,必要的时候可以直接使用FFmpeg采集。SDL本身是一个多媒体库,其最强大的地方不是在音频上,而是在图形显示上。这里只是使用SDL来播放音频。

SDL下载地址

SDL下载地址

SDL的linux下编译

配置选项:

./configure CC=arm-linux-gnueabihf-gcc --host=arm-linux --prefix=/grapeRain/grape_SDL2 --disable-static --enable-shared --disable-pulseaudio --disable-esd
make -j4
make install

注:--disable-pulseaudio --disable-esd是因为编译之后出现错误,所以我disable了这两个选项,之后编译就通过了,所以我也不知道这两个编译选项的含义。

SDL的使用

参考从零开始学习音视频编程技术(七) FFMPEG Qt视频播放器之SDL的使用中提供的项目源码。
注意:在FFmpeg3.4.1这个版本中,avcodec_alloc_frame()函数已经废弃了,使用av_frame_alloc()替换。同时,avcodec_get_frame_defaults()也已经废弃,具体可以查看FFmpeg的文档。文档路径为:/xxx/ffmpeg-3.4.1/doc/下的APIchanges,里面记录了FFmpeg的API改动。
参考FFmpeg API 变更记录


SDL播放音频是通过回调函数的方式播放,且这个回调函数是在新的线程中运行,此回调函数固定时间激发一次,这个时间和要播放的音频频率有关系。(和Qt的定时器有点像)
因此,用FFmpeg读到一帧音频后,不是着急解码,而是将数据存入一个队列,等SDL回调函数激发的时候,从这个队列中取出数据,然后解码播放。

SDL的播放音频

播放音频流程

  1. 初始化
    1) 初始化SDL
    2) 根据参数(SDL_AudioSpec)打开音频设备
  2. 循环播放数据
    1) 播放音频数据
    2) 延时等待播放完成

SDL结构体

  • SDL_AudioSpec
typedef struct SDL_AudioSpec  
{  
    int freq;                   音频数据采样率 常用48000 44100
    SDL_AudioFormat format;     音频数据的格式。
    Uint8 channels;             声道数。例如单声道取值为1,立体声取值为2
    Uint8 silence;              设置静音的值
    Uint16 samples;             音频缓冲区中的采样个数,要求必须是2的n次方
    Uint16 padding;             考虑到兼容性的一个参数
    Uint32 size;                音频缓冲区的大小,以字节为单位
    SDL_AudioCallback callback; 填充音频缓冲区的回调函数
    void *userdata;             用户自定义的数据
} SDL_AudioSpec; 

注:SDL_AudioFormat format;—-常用的音频数据格式:

AUDIO_U16SYS:unsigned 16-bit samples
AUDIO_S16SYS:Signed 16-bit samples
AUDIO_S32SYS:32-bit integer samples
AUDIO_F32SYS:32-bit floating point samples

SDL的API

  • SDL_Init()
// 功能:初始化SDL
int SDLCALL SDL_Init(Uint32 flags);
// 参数:flags
SDL_INIT_TIMER:定时器
SDL_INIT_AUDIO:音频
SDL_INIT_VIDEO:视频
SDL_INIT_JOYSTICK:摇杆
SDL_INIT_GAMECONTROLLER:游戏控制器
SDL_INIT_NOPARACHUTE:不捕获关键信号
SDL_INIT_EVERYTHING:包含上述所有选项
  • SDL_OpenAudio()
    SDL_OpenAudio打开SDL播放设备
  • SDL_PauseAudio()
// 功能:当pause_on设置为0即开始播放音频数据。设置为1的时候,将会播放静音的值
void SDLCALL SDL_PauseAudio(int pause_on)
  • 回调函数
    当音频设备需要更多数据时,会调用这个回调函数。回调函数格式如:
void (SDLCALL * SDL_AudioCallback) (void *userdata, Uint8 * stream,  
                                            int len);  
userdata: SDL_AudioSpec结构中的用户自定义数据,一般情况可以不用。
stream: 该指针指向需要填充的音频缓冲区

SDL互斥量和条件变量

在进行音视频播放的过程中,会把解复用和解码放在不同的线程中,存放包的队列是公共资源,需要互斥访问。具体就是解复用向队列中添加包,解码从队列中取包,也就需要同步。所以在队列的入队和出对操作中,采用了互斥量和条件变量。

互斥量

  • 创建互斥量
SDL_mutex *mutex;
mutex = SDL_CreateMutex();

创建的互斥量默认是未上锁的。
- 上锁和解锁

SDL_LockMutex(mutex);
SDL_UnlockMutex(mutex);
  • 销毁锁
SDL_DestroyMutex(mutex);

条件变量

  • 创建条件变量
SDL_cond* SDL_CreateCond(void) 
  • 等待条件变量
int SDL_CondWait(SDL_cond*  cond,  
                 SDL_mutex* mutex) 

信号激活后,返回0,否则返回错误代码。
SDL_CondWait必须在互斥量锁住之后才能调用。该函数会解锁锁住的互斥量,并等待拥有该锁的线程激活信号。激活后,重新上锁。
- 激活信号

int SDL_CondSignal(SDL_cond* cond)  

SDL_CondSignal会激活等待的一个线程(根据优先级),而不是所有等待的线程。


Linux 线程同步—条件变量

代码

typedef struct PacketQueue
{
    AVPacketList *first_pkt, *last_pkt;
    int nb_packets;
    int size;
    SDL_mutex *mutex;
    SDL_cond *cond;
}PacketQueue_t;
void packet_queue_init(PacketQueue *q)
{
    memset(q, 0, sizeof(PacketQueue));
    q->mutex = SDL_CreateMutex();
    q->cond  = SDL_CreateCond();
    q->size  = 0;
    q->nb_packets = 0;
    q->first_pkt  = NULL;
    q->last_pkt   = NULL;
}

int packet_queue_put(PacketQueue *q, AVPacket *pkt)
{
    AVPacketList *pkt1;
    if (av_dup_packet(pkt) < 0) {
        return -1;
    }

    pkt1 = (AVPacketList*)av_malloc(sizeof(AVPacketList));
    if (!pkt1)
    {
        return -1;
    }
    pkt1->pkt = *pkt;
    pkt1->next = NULL;

    SDL_LockMutex(q->mutex);
    if(!q->last_pkt)
    {
        q->first_pkt = pkt1;
    }
    else
    {
        q->last_pkt->next = pkt1;
    }

    q->last_pkt = pkt1;             // pointer to the add one packet
    q->nb_packets++;
    q->size += pkt1->pkt.size;

    SDL_CondSignal(q->cond);
    SDL_UnlockMutex(q->mutex);

    return 0;
}

int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block)
{
    AVPacketList *pkt1;
    int ret;

    SDL_LockMutex(q->mutex);

    for(;;)
    {
        pkt1 = q->first_pkt;
        if(pkt1)
        {
            q->first_pkt = pkt1->next;
            if(!q->first_pkt)
            {
                q->last_pkt = NULL;
            }
            q->nb_packets--;
            q->size -= pkt1->pkt.size;
            av_free(pkt);
            ret = 1;
            break;
        }
        else if(!block)
        {
            ret = 0;
            break;
        }
        else
        {
            SDL_CondWait(q->cond, q->mutex);
        }
    }

    SDL_UnlockMutex(q->mutex);

    return ret;
}

理解来说,假设packet_queue_put是一个生产者,一直往队列里填数据;而packet_queue_get是消费者,一直从队列中取数据。当packet_queue_get从队列中取数据时,发现队列中已经没有数据了,就会调用SDL_CondWait进入等待状态(阻塞),同时释放互斥锁。这时候,packet_queue_put往队列中填入数据后,调用SDL_CondSignal,唤醒packet_queue_get读取数据,并重新上锁。在读取数据完毕之后,释放锁。
博主记:以上是博主的个人理解,可能有不对的地方,见谅!!

FFmpeg函数解析

结构体

  • AVPacket
    每一个Packet是一个完整的帧,用来暂存解复用之后、解码之前的媒体数据
typedef struct AVPacket  
{  
    int64_t pts;    // 显示时间戳
    int64_t dts;    // 解码时间戳
    int64_t pos;  
    uint8_t *data;  // 数据首地址  
    int size;  
    int stream_index; // 所属媒体流的索引  
    int flags;        // flags为标志域,1表示该数据是一个关键帧  
    void(*destruct)(struct AVPacket*);  // 释放数据缓冲区的函数指针  
} AVPacket;  

AVPacket本身只是一个容器,其中data成员指向实际的数据缓冲区。这个缓冲区通常由av_new_packet创建,也可能由FFMPEG的API创建。当某个AVPacket结构的数据缓冲区不再被使用时,需要通过调用av_free_packet释放。

FFmpeg内部使用AVPacket建立缓冲区装载数据,同时提供destruct函数,如果FFmpeg打算自己维护缓冲区,则将destruct设为av_destruct_packet_nofree,用户调用av_free_packet清理缓冲区时并不能将其释放(共享缓冲区);如果FFmpeg打算将该缓冲区彻底交给调用者,则将destruct设为av_destruct_packet,表示它能够被释放。安全起见,如果用户希望自由使用一个FFmpeg内部创建的AVPacket,最好调用av_dup_packet进行缓冲区的克隆,将其转化为缓冲区能够被释放的AVPacket,以免对缓冲区的不当占用造成异常错误。av_dup_packet会为destruct指针为av_destruct_packet_nofree的AVPacket新建一个缓冲区,然后将原缓冲区的数据拷贝至新缓冲区,设置data的值为新缓冲区的地址,同时设置destruct指针为av_destruct_packet。
- AVPacketList

typedef struct AVPacketList {  
    AVPacket pkt;       // AVPacket 
    struct AVPacketList *next; // next是一个AVPacketList指针  
} AVPacketList;  
  • PacketQueue
typedef struct PacketQueue {  
  AVPacketList *first_pkt, *last_pkt; // first_pkt指向AVPacketList的头 last_pkt指向AVPacketList的尾  
  int nb_packets;  // Packet队列的中AV_Packet的个数  
  int size;  
  SDL_mutex *mutex;  
  SDL_cond *cond;  
} PacketQueue;

函数

  • av_dup_packet(AVPacket* pkt)
int av_dup_packet(AVPacket *pkt)
{
    if (((pkt->destruct == av_destruct_packet_nofree) || (pkt->destruct == NULL)) && pkt->data) {
    uint8_t *data;

    if((unsigned)pkt->size > (unsigned)pkt->size + FF_INPUT_BUFFER_PADDING_SIZE)
    return AVERROR(ENOMEM);
    data = av_malloc(pkt->size + FF_INPUT_BUFFER_PADDING_SIZE);
    if (!data) {
        return AVERROR(ENOMEM);
    }
    memcpy(data, pkt->data, pkt->size);
    memset(data + pkt->size, 0, FF_INPUT_BUFFER_PADDING_SIZE);
    pkt->data = data;
    pkt->destruct = av_destruct_packet;
    }
    return 0;
}

av_destruct_packet_nofree表示AVPacket由FFmpeg维护,换句话来说,就是由FFmpeg来管理释放。既然由FFmpeg来管理,作用可以用于共享,推测就是数据可以拷贝不可以被随意修改,如果需要数据被修改,需要使用av_dup_packet拷贝一份数据。

总结

参考链接

最简单的视音频播放示例9:SDL2播放PCM
FFmpeg API 变更记录
从零开始学习音视频编程技术(十) FFMPEG Qt视频播放器之播放控制
ffmpeg之AVPacket笔记
FFMPEG AVPacket
SDL中的互斥量和条件变量
Linux 线程同步—条件变量

ffmpeg(一) ffmpeg+QT开发环境搭建 (exe 程序编译的时候只需要包含 xxx.dll.a 静态导入库,运行的时候才需要 xxx.dll 文件)这个 .pro 文件的配置,是指导程序编译的时候,可以找到对应的动态库。值得一提的是,在Windows中,静态链接库(Static Link Library)的扩展名也是.lib、.dll.a。其实只要你掌握了FFmpeg,也可以很快上手其他音视频开发库,因为底层原理都是一样的,你最终操作的都是一样的数据,比如MP3、MP4文件。导入库:不包含函数的具体实现代码(函数的具体实现代码存储在dll中) 阅读详情

相关推荐

QT配置FFmpeg库

环境准备:QT 5.12.1。

qiannozongheng的博客 1711

Qt使用ffmpeg

最终,还是逃不过要使用这个,一直看到却一直没正式了解它的库:ffmpeg。 这个库简单的说就是用来对视频进行各种处理的:读取、格式转换、播放等等。 目前我是在windows下,在Qt里面使用它的。 首先,到这里下载(通过官网也可以跳转到这里)。 我下载的是 ffmpeg-n4.4-latest-win64-lgpl-shared-4.4。你可以根据自己的需求下载。 然后在Qt里面引入库以及头文件 INCLUDEPATH += D:/Qt/ffmpeg/ffmpeg-n4.4-latest-win64-lgp

joyopirate的博客 8487

FFMpeg-3、基于QT实现音视频播放显示

为什么要采用QT的OpenGL三维的来绘制,直接使用QopenWidget、QWight也是可以的,但是如果使用后面的那么其实图像显示和界面的按钮是一套东西,当点击按钮刷新的时候就会存在闪屏的情况。因为视频显示存在像素格式的转换问题,要从解码出来的YUV格式转化为显示需要的RGB格式,视频的每一帧图像这么多像素点都需要转换,这是一个很大的开销,如果这一部分效率不高,那么对整体的视频播放性能都是十分有影响的,而QTopenGL也是效率很高的,直接操作显卡。后台缓冲表面和前台表面的概念总是同时出现的。

yinshipin007的博客 2711

基于qt和ffmpeg视频播放器开发实战

ffmpeg是世界上应用广的开源视频处理框架,在各个平台(windows,linux,嵌入式、Android、IOS)被广泛应用,可以说是做视频相关项目技能qt是目前跨平台项目界面开发的第一选择,本课程基于这两种框架在加上VC++,给大家讲解延时如何开发出一个视频播放器,各个模块如何结合,如何考虑性能问题,如何设计封装类,如何处理各种异常情况。

Qt + FFmpeg 视频播放器

一、 环境搭建 1. 下载 QT:window 5.7.0版本 FFmpeg: ffmpeg-20200522-38490cb-win32-dev 注意:这里下载 32位dev版本,要和编译器对应(我的mingw是32位的) 2. 加载库 在FFmpeg的解压目录里 将 include/ 的头文件拷贝到自己的项目的工程路径下 从 lib/ 拷贝所需要的库到自己的项目里 #这里是我所用到的库和 pro里的配置 //avcodec-58.dll //avdevice-58.dll //avfilte

qq_40714324的博客 4575

基于FFmpeg的Qt视频播放器

操作系统: win10 64位QT版本: QT5.14.0编译器: MinGW 64FFMPEG版本: 4.2.2。

yishui的博客 2640

利用QT和FFmpeg实现一个简单的视频播放器

本示例使用Qt和FFmpeg构建一个简单的视频播放器。利用ffmpeg解码视频,通过QWidget渲染解码后的图像,支持进度条跳转、进度条显示,总时间显示,视频基本信息显示。特点: 采用软件解码(CPU)、只解码图像数据,主要是演示了ffmpeg的基本使用流程,如何通过ffmpeg完成视频解码,转换图像像素格式,最后完成图像渲染。视频解码采用独立子线程,解码后将得到的图像数据通过信号槽发方式传递给UI界面进行渲染。

u012959478的博客 3217

基于QT和FFmpeg实现自己的视频播放器FFMediaPlayer(一)——项目总览

本文介绍了基于QT和FFmpeg开发自定义视频播放器FFMediaPlayer的项目设计思路、架构与配置。首先,文章强调了遵循软件设计五大原则(单一职责、开放封闭、里氏替换、接口隔离、依赖倒置)的重要性,以确保项目的可维护性和扩展性。接着,通过类图展示了项目的核心架构,分为窗口交互层和解封装解码层,详细描述了各模块的功能和交互方式。最后,文章提供了项目的开发环境配置,包括操作系统、开发工具、库依赖等,并简要说明了新项目的创建步骤。本文为系列文章的开篇,后续将深入探讨视频解码、音频处理和界面渲染等具体功能的实

2301_76564925的博客 1519

Qt tips 之使用 FFmpeg + OpenGL 实现简易视频播放器

Qt + FFmpeg + OpenGL 实现简易视频播放器

qq_41368848的博客 1527

Qt+FFmpeg+OpenGL实现视频播放器(1)

通过解码,将压缩编码的视频数据 H.264,MPEG2 解码成为非压缩的颜色数据,例如 YUV 等等;将压缩编码的音频数据 AAC,MP3 解码成为非压缩的音频抽样数据,例如 PCM 数据。例如,FLV 格式的数据,经过解封装操作后,输出 H.264 编码的视频码流和 AAC 编码的音频码流。音视频同步:就是根据解封装模块处理过程中获取到的参数信息,同步解码出来的音频和视频数据,并将音视频频数据送至系统的显卡和声卡播放出来(Render)。输出文件的详细信息,包括音视频流的信息。来解析视频文件中的流信息。

zaomengze的博客 1883

QT软件开发-基于FFMPEG设计视频播放器-软解图像(一)

为了能简单快速的介绍ffmpeg使用,这里连续通过几篇文章,编写几个案例来循序渐进的演示ffmpeg的用法,最终完成一个完整的播放器开发,不涉及理论知识(理论知识网上太多了),主要是以代码、以实现功能为主。

5786

QT+ffmpeg+多线程的视频播放器的基本使用

一、简述 视频播放器实质是快速的播放图片,一般情况下,人的眼睛一秒可以扫过25帧图片,这样看起来有视频播放的效果。有的人可能会想到通过把视频全部解码完,然后把解码后得到所有的图片保存下来再开始播放,那么问题来了,如果这个视频非常的大(有好几百兆),就会导致播放器都打开了迟迟没有响应的情况,使得体验感大大下降。 因此,我们可以进行边解码边播放,才有时效性,所以就引入了线程实现并发(详见我的博客:多线程的简单使用,这里不做过多阐述),下面就来实现边解码边在窗口播放的效果。 二、实现效果 三、基本实现

hml111666的博客 3697

FFmpeg与Qt结合开发视频播放器实战教程

libavcodec是FFmpeg项目中最为核心的一个库,提供了丰富的音视频编解码器,支持几乎所有已知的视频和音频格式的编解码。开发者可以利用libavcodec进行音视频数据的压缩和解压缩操作。在使用libavcodec时,一般需要先初始化编解码器上下文(),然后通过注册编解码器()和查找编解码器(或)来准备编解码操作。if (!codec) {// 编解码器查找失败处理if (!// 编解码器上下文分配失败处理// 打开编解码器// 打开编解码器失败处理。

weixin_30481539的博客 778

FFmpeg开发(四)——Qt实现一个视频播放器(参考了暴风影音、迅雷影音)

FFmpeg开发(四)——Qt实现一个视频播放器(参考了暴风影音、迅雷影音) 前几篇介绍了: FFmpeg开发(一)一Qt Creator配置FFmpeg FFmpeg开发(二)——(FFmpeg基础知识介绍) 在前几篇的基础上,写了一个视频播放器。参考了暴风影音和迅雷影音。 开发工具:Qt 5.8.0 FFmpeg版本:4.2.1. 操作系统:Windows 10 先来几张播放器的图片: 由于版权问题,只是参考了暴风影音和迅雷影音的界面布局,并没有做成一样....

ifeng12358的博客 1万+

Qt 基于FFmpeg的视频播放器 - QtFFmpegPlayer

- `本文基于FFmpeg,使用Qt制作了一个极简的视频播放器. 如图所示:` - FFmpeg版本信息:`ffmpeg-n7.0-latest-win64-lgpl-shared-7.0`

做减求空 3881

TVS 和压敏电阻联合使用进行浪涌保护时,压敏电阻的压敏电压要低于TVS 的钳位电压 VC。

通过先让MOV接管,可以确保在大多数情况下,电压不会迅速上升到TVS二极管的钳位电压,从而保护TVS二极管免受持续高能量冲击的损害。总结来说,将MOV的压敏电压设置得低于TVS二极管的钳位电压,可以提供一种分级的保护机制,优化浪涌保护性能,同时保护敏感的TVS二极管不被过度的能量冲击损坏,并延长其使用寿命。MOV吸收的能量(Wmov):假设浪涌电流为10kA,MOV导通后将电压限制在300V,那么Wmov = Ipp * Vmov = 10kA * 300V = 3MW。例如,Vmov可以设定为275V。

weixin_66642980的博客 2247

Qt+FFmpeg视频播放器开发(三)

音视频基本概念+ffmpeg核心类介绍

2401_82413779的博客 1000

信贷风控实战指南:Vintage分析表从数据建模到风险洞察

本文是一份信贷风控实战指南,详细阐述了Vintage分析从数据建模到风险洞察的全流程。文章深入解析了Vintage分析作为信贷资产“体检报告”的核心价值,并提供了从借据表与还款计划表设计、账龄计算、逾期口径判定到风险暴露计算与最终可视化透视的完整SQL实现方案,旨在帮助风控从业者构建精准的风险监控体系。

nft7creator的博客 321

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

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

c_shell_python的博客 5922

Security Onion的部署(超详细)

本文介绍了开源入侵检测系统securityonion的部署

qq_72583325的博客 6070

国科GK7202V300芯片开发避坑指南:从uboot编译到SDK配置的12个实战问题解决

本文针对国科GK7202V300芯片开发过程中的常见难题,提供从uboot编译、SDK配置到系统部署的实战解决方案。详细解析了开发环境搭建、内核编译、驱动模块加载、根文件系统构建及烧写部署等12个关键环节的避坑指南,帮助开发者高效解决编译错误、配置冲突等典型问题,提升开发效率。

sunny的博客 235
上一篇: Linux网络编程(三)
下一篇: opencv相似图片搜索
simonXi-tech
博客等级 码龄10年 398粉丝 52原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值