对于音频部分,主要从以下几个部分实现。

1、音频播放的启动、停止、暂停、缓冲写入接口实现

2、ffmpeg音频解码器打开和音频解码

3、ffmpeg音频重采样标准化音频的输出格式

4、多线程和缓冲队列实现音视频同步播放

一、音频播放的启动和停止接口实现

首先我们创建一个音频播放类XAudioPlay,使用单例模式,提供一个接口,在XAudioPlay.cpp中对此接口重新实现,申明如下。

#pragma once
class XAudioPlay
{
public:XAudioPlay *Get();//单例模式virtual bool Start()=0;//启动virtual void  Play(bool isplay)=0;//暂停virtual bool Write(const char *data,int datasize) = 0;//将音频写入virtual void Stop()=0;//停止virtual int GetFree() = 0;//获取剩余空间virtual ~XAudioPlay();int sampleRate = 48000;//样本率int sampleSize = 16;//样本大小int channel = 2;///通道数
protected:XAudioPlay();
};

对启动、停止、暂停、缓冲写入函数的定义如下。

#include "XAudioPlay.h"
#include <QAudioOutput>
#include<QMutex>
class CXAudioPlay :public XAudioPlay
{
public:QAudioOutput *output = NULL;QIODevice *io = NULL;QMutex mutex;void Stop(){mutex.lock();if (output)//为打开AudioOutput{output->stop();delete output;output = NULL;io = NULL;}mutex.unlock();}//首先设置播放的格式以及参数bool Start(){Stop();mutex.lock();QAudioOutput *out;//播放音频QAudioFormat fmt;//设置音频输出格式fmt.setSampleRate(48000);//1秒的音频采样率fmt.setSampleSize(16);//声音样本的大小fmt.setChannelCount(2);//声道fmt.setCodec("audio/pcm");//解码格式fmt.setByteOrder(QAudioFormat::LittleEndian);fmt.setSampleType(QAudioFormat::UnSignedInt);//设置音频类型output = new QAudioOutput(fmt);io = output->start();//播放开始mutex.unlock();return true;}void  Play(bool isplay){mutex.lock();if (!output){mutex.unlock();return;}if (isplay){output->resume();//恢复播放}else{output->suspend();//暂停播放}mutex.unlock();}int GetFree(){mutex.lock();if (!output){mutex.unlock();return 0;}int free = output->bytesFree();//剩余的空间mutex.unlock();return free;}bool Write(const char *data, int datasize){mutex.lock();if (io)io->write(data, datasize);//将获取的音频写入到缓冲区中mutex.unlock();return true;}
};XAudioPlay::XAudioPlay()
{
}XAudioPlay::~XAudioPlay()
{
}XAudioPlay * XAudioPlay::Get()
{static CXAudioPlay ap;return &ap;}

这里在我们缓冲区写入时,我们先得保证缓冲区有空间让我们写入,所以这里的GetFree()函数用来检测当前缓冲区大小。

二、ffmpeg音频解码器打开和音频解码

之前我们在XFFmpeg.cpp中Open视频时,我们只对视频处理了,音频并未处理,现在我们在Open中进行音频处理,先打开音频解码器,在.h中定义

audioStream//音频流int sampleRate = 48000;//样本率
int sampleSize = 16;//样本大小
int channel = 2;///通道数

在XFFmpeg.cpp中在判断视频流后面加入判断音频流如下:

else if (enc->codec_type == AVMEDIA_TYPE_AUDIO)//若未音频流{audioStream = i;//音频流AVCodec *codec = avcodec_find_decoder(enc->codec_id);//查找解码器if (avcodec_open2(enc, codec, NULL) < 0){mutex.unlock();return 0;}this->sampleRate = enc->sample_rate;//样本率this->channel = enc->channels;//通道数switch (enc->sample_fmt)//样本大小{case AV_SAMPLE_FMT_S16://signed 16 bitsthis->sampleSize = 16;break;case  AV_SAMPLE_FMT_S32://signed 32 bitsthis->sampleSize = 32;default:break;}printf("audio sample rate:%d sample size:%d chanle:%d\n",this->sampleRate,this->sampleSize,this->channel);}

现在我们考虑一下音频解码,在XFFmpeg.cpp的Decode()的函数中,有avcodec_send_packet()函数用来发送数据包,是用同一个接口发送音频和视频数据的,那就会有一个问题,在我们后面使用解码后音频和视频是否可以存放在一起,因为后面我们需要做音频、视频得同步,也就是说音频和视频需要多线程播放的,这样在同步播放时会有一些问题。所以这里给音频再增加一块内存。首先在.h中增加解码后存放音频的申明,现在又有一个问题,之前我们的播放是以视频为准的,可以通过视频控制播放速度,在增加音频后,我们的视频依旧可以适应音频播放,但音频尽量不要适应视频播放,因为一旦控制了音频的播放速度,就可能导致音频的失真。在XFFmpeg..h中我们存了一个pts,所以这里在解码后是音频时,我们处理这个pts,为视频时不改变它,那么视频又如何跟上音频呢,这里我们需要改写AVFrame * XFFmpeg::Decode(const AVPacket *pkt),之前我们返回的是解码后的视频帧,现在我们返回显示时间。

int XFFmpeg::Decode(const AVPacket *pkt)
{mutex.lock();if (!ic)//若未打开视频{mutex.unlock();return NULL;}if (yuv == NULL)//申请解码的对象空间{yuv = av_frame_alloc();}if (pcm == NULL){pcm = av_frame_alloc();}AVFrame *frame = yuv;//此时的frame是解码后的视频流if (pkt->stream_index == audioStream)//若未音频{frame = pcm;//此时frame是解码后的音频流}int re = avcodec_send_packet(ic->streams[pkt->stream_index]->codec, pkt);//发送之前读取的pktif (re != 0){mutex.unlock();return NULL;}re = avcodec_receive_frame(ic->streams[pkt->stream_index]->codec, frame);//解码pkt后存入yuv中if (re != 0){mutex.unlock();return NULL;}qDebug() << "pts=" << frame->pts;mutex.unlock();int p = frame->pts*r2d(ic->streams[pkt->stream_index]->time_base);//当前解码的显示时间if (pkt->stream_index == audioStream)//为音频流时设置ptsthis->pts = p;return p;
}

三、ffmpeg音频重采样标准化音频的输出格式、以及视音播放

音频的重采样和视频得转化有很多相同之处,我们在.h中申明int ToPCM(char *out);//音频的重采样,以及SwrContext *aCtx = NULL;//音频重采样上下文,在.cpp中定义如下

int XFFmpeg::ToPCM(char *out)
{mutex.lock();if (!ic || !pcm || !out)//文件未打开,解码器未打开,无数据{mutex.unlock();return 0;}AVCodecContext *ctx = ic->streams[audioStream]->codec;//音频解码器上下文if (aCtx == NULL){aCtx = swr_alloc();//初始化swr_alloc_set_opts(aCtx,ctx->channel_layout,AV_SAMPLE_FMT_S16,ctx->sample_rate,ctx->channels,ctx->sample_fmt,ctx->sample_rate,0,0);swr_init(aCtx);}uint8_t  *data[1];data[0] = (uint8_t *)out;//音频的重采样过程int len = swr_convert(aCtx, data, 10000,(const uint8_t **)pcm->data,pcm->nb_samples);if (len <= 0){mutex.unlock();return 0;}int outsize = av_samples_get_buffer_size(NULL, ctx->channels,pcm->nb_samples,AV_SAMPLE_FMT_S16,0);mutex.unlock();return outsize;
}

在ffmpeg里面使用av_sample_get_buffer_size来计算音频占用的字节数的。现在我们来实现音视频的播放,首先在aginexplay.cpp中的open函数中对音频重采样的采样率、通道数、采样大小进行设置,我们这里使用原视频中的数据,然后启动音视频的重采样。如下。

void agineXplay::open()
{QString name = QFileDialog::getOpenFileName(this, QString::fromLocal8Bit("选择视频文件"));//打开视频文件if (name.isEmpty())return;this->setWindowTitle(name);//设置窗口的标题int totalMs = XFFmpeg::Get()->Open(name.toLocal8Bit());//获取视频总时间if (totalMs <= 0)//未打开成功{QMessageBox::information(this, "err", "file open failed!");//弹出错误窗口return;}XAudioPlay::Get()->sampleRate = XFFmpeg::Get()->sampleRate;XAudioPlay::Get()->channel = XFFmpeg::Get()->channel;XAudioPlay::Get()->sampleSize = 16;XAudioPlay::Get()->Start();//启动音频char buf[1024] = { 0 };//用来存放总时间int min = (totalMs) / 60;int sec = (totalMs) % 60;sprintf(buf, "%03d:%02d", min, sec);//存入buf中ui.totaltime->setText(buf);//显示在界面中play();
}

,设置好音频的重采样的格式后,现在我们需要达到播放音频的效果,考虑音频的播放要求高于视频每帧的播放,我们在XVideoThread.cpp中进行如下修改。

void XVideoThread::run()
{char out[10000] = {0};while (!isexit)//线程未退出{if (!XFFmpeg::Get()->isPlay)//如果为暂停状态,不处理{msleep(10);continue;}int free = XAudioPlay::Get()->GetFree();//此时缓冲区的空间大小if (free < 10000){msleep(1);continue;}AVPacket pkt = XFFmpeg::Get()->Read();if (pkt.size <= 0)//未打开视频{msleep(10);continue;}if (pkt.stream_index == XFFmpeg::Get()->audioStream){XFFmpeg::Get()->Decode(&pkt);//解码音频av_packet_unref(&pkt);//释放pkt包int len = XFFmpeg::Get()->ToPCM(out);//重采样音频XAudioPlay::Get()->Write(out, len);//写入音频continue;}XFFmpeg::Get()->Decode(&pkt);//解码视频帧av_packet_unref(&pkt); }}

其中这里的GetFree()函数是确保在我们在写入音频时,此时它的缓冲区还能存放下该帧音频,避免后面音频读取出错。

四、多线程和缓冲队列实现音视频同步播放

上面所用到的在我播放本地视频时不会有什么问题,但是当我们在播放网络视频时可能就会出现音视频不同步,声音错落的现象,基本上经常出现的是视频跟不上音频的状况,有时也会出现音频跟不上视频得现象,这样我们没办法通常只能减少视频得帧画面来降低视频的播放。这里我们如何来同步音视频呢,一种方法就是我们采用一个List链表,将每次AVPacket到音频前的视频帧存放进去,在我们读取到音频解码时,同时将List链表里所有的视频帧解码出来进行播放,保证了音视频的播放,如何处理呢?首先我们需要解码后音视频的Pts,之前在XFFMpeg.cpp中有个Decode()函数,我们已经进行了修改返回的是解码后的pts,所以这里我们在XVideoThread.cpp中当读取到为音频是,记录下它的pts,再处理list链表中的AVPacket的视频帧进行解码,具体过程下方有详细说明。

#include "XVideoThread.h"
#include "XFFmpeg.h"
#include "XAudioPlay.h"
#include <list>
using namespace std;static list<AVPacket> videos;//用来存放解码前的视频帧
bool isexit = false;//线程未退出
static int apts = -1;//音频的pts
XVideoThread::XVideoThread()
{
}XVideoThread::~XVideoThread()
{
}void XVideoThread::run()
{char out[10000] = {0};while (!isexit)//线程未退出{if (!XFFmpeg::Get()->isPlay)//如果为暂停状态,不处理{msleep(10);continue;}while (videos.size()>0)//确定list中是否有AVpacket包{AVPacket pack = videos.front();//每次取出list中的第一个AVPack包int pts = XFFmpeg::Get()->GetPts(&pack);//获得该包的ptsif (pts > apts)//若视屏包大于音频包的pts,结束{break;}XFFmpeg::Get()->Decode(&pack);//解码视频帧av_packet_unref(&pack);//清理该AVPacket包videos.pop_front();//从list链表中删除}int free = XAudioPlay::Get()->GetFree();//此时缓冲区的空间大小if (free < 10000){msleep(1);continue;}AVPacket pkt = XFFmpeg::Get()->Read();if (pkt.size <= 0)//未打开视频{msleep(10);continue;}if (pkt.stream_index == XFFmpeg::Get()->audioStream){apts = XFFmpeg::Get()->Decode(&pkt);//解码音频av_packet_unref(&pkt);//释放pkt包Sint len = XFFmpeg::Get()->ToPCM(out);//重采样音频XAudioPlay::Get()->Write(out, len);//写入音频continue;}videos.push_back(pkt);}}

此时无论是本地视频还是网络视频我们播放时都可以完成音视频的同步问题,至此音频的处理也已完毕,整个音视频播放的也已结束,其中涉及到的内容有如下几点:

FFMpeg,Qt带有的QOpenGL绘制视频(后面有用OpenGL通过GPU间接绘制视频的代码链接)、多线程实现音频视频得读取、解码以及音频的重采样,而视频的转码绘制单独在VideoWidget.cpp中实现.

效果图:

效果图代码链接:https://download.csdn.net/download/hfuu1504011020/10672140

基于Qt、FFMpeg的音视频播放器设计五(FFMpeg音频实现)相关推荐

  1. 基于Qt、FFMpeg的音视频播放器设计一

    前言:整个项目的源代码 https://download.csdn.net/download/hfuu1504011020/10672140 最近刚完成基于Qt.FFMpeg的音视频播放器相关C++程 ...

  2. 基于QT实现简易音视频播放器

    目录:         一.界面布局         二.播放本地音频                 2.1 打开本地音频保存路径                 2.2 选中想要播放的音频加入到播 ...

  3. 基于Qt、FFMpeg的音视频播放器设计四(视频播放进度控制)

    上面介绍了如何使用opengl绘制视频和Qt的界面设计,也比较简单,现在我们看下如何控制视频播放及进度的控制,内容主要分为以下几个部分 1.创建解码线程控制播放速度 2.通过Qt打开外部视频 3.视频 ...

  4. 基于QT封装的音视频播放时间轴控件

    采用QT graphicsview视图框架,可以实现时间轴缩放,指针拖拉,滚动条移动,可以新增指针事件等,提供时间片添加接口. 思路:左侧车牌信息和通道列表是qwidget正常的窗口,右侧的时间轴,通 ...

  5. 基于Phonon+QT的音视频播放器设计与实现

    目 录 摘 要 2 第一章 软件需求说明书 1 1.1 引言 1 1.2 业务流程整体说明 1 第二章 需求分析报告 3 2.1 引言 3 2.2 任务概述 3 2.3 功能需求 3 2.4 性能需求 ...

  6. 基于FFmpeg的音视频播放器

    版本信息 AndroidStudio 3.5.2 FFmpeg 4.0.2 背景 AndroidStudio3.5.1下搭建FFmpeg环境 Android使用FFmpeg动态库播放视频 Androi ...

  7. QML+Qt音视频播放器

    代码地址 xyygudu/Player: Qt和QML实现了视频播放器和音乐播放器 (github.com) 部分效果展示 实现的功能 视频相关:播放暂停.播放进度调节.音量调节.列表显示指定目录下的 ...

  8. QT + FFmpeg 5.x + x264 + x265 + SDL2 音视频播放器

    QT + FFmpeg 5.x + x264 + x265 + SDL2 音视频播放器 使用了QT的QML设计界面,人机交互; 使用了FFmpeg 5.x + x264 + x265 + SDL2 完 ...

  9. Qt FFmpeg 音视频播放器

    使用FFmpeg库实现 本地和rtp 音视频播放器,使用qt绘制视频. 本demo环境为 qt5.12 vs2019-32位 .pro的qt工程 FFmpeg版本位3.4.8 vs2092-32位 本 ...

  10. 视频教程-基于NDK、C++、FFmpeg的android视频播放器开发实战-Android

    基于NDK.C++.FFmpeg的android视频播放器开发实战 夏曹俊:南京捷帝科技有限公司创始人,南京大学计算机硕士毕业,有15年c++跨平台项目研发的经验,领导开发过大量的c++虚拟仿真,计算 ...

最新文章

  1. require引入js vue_请教 关于使用require 引入vue 和公共js的问题
  2. 使用J-flash arm下载程序
  3. HDU——1257最少拦截系统(贪心)
  4. -webkit-filter
  5. 据所有独立的c文件生成相应执行文件通用Makefile
  6. Stolz定理及其证明
  7. 中国互联网络发展状况统计报告计算机,中国互联网络发展状况统计报告-计算机网络信息中心.DOC...
  8. 怎么将图片镜面对称_怎么把镜面对称
  9. Aseprite Dark Mort HD 主题
  10. PTA - 数据库合集3
  11. muduo学习笔记:base部分之高性能日志库-Logger
  12. 【英语-同义词汇词组】therefore、hence、thus的用法及区别
  13. html用图片代替color,Image Colorizer - 将黑白照片变为彩色照片在线工具
  14. HEVC BLA、CRA、IDR
  15. 《数据结构(C语言版)》严巍敏课件~第七章:图
  16. 孤岛危机 教程:使用Voxel技术创建地形
  17. 在Ubuntu KyLin 16.04上安装g2o
  18. LSP协议被劫持,导致无法上网
  19. Git 如何把master的内容更新到分支
  20. a as as big rat_励志英语名言-20页

热门文章

  1. 【慕课网】JavaScript中函数和this
  2. 梦幻西游网页版服务器,服务器荣辱战,《梦幻西游网页版》梦幻攻防战“挖矿人”经验来啦...
  3. 洛谷4315 月下“毛景树”(树链剖分)
  4. 互联网让我的人生逆袭
  5. selenium+python爬取京东评论最多的计算机配置信息
  6. 脉冲神经网络SNN的简介
  7. Self-Supervised Learning of Pretext-Invariant Representation
  8. python 自动解4399数独游戏
  9. Rasa_nlu_chi:入门教程
  10. 天池大数据《快来一起挖掘幸福感!》项目第169名