接着上一次的工程,这次把读取音频包跟解码音频包放到不同的线程里面去。而且找到了播放卡顿的原因,按照网上大神的说法,就是输出缓冲区out_size的大小要跟随swr_convert的返回值,具体取值为
out_size = swr_convert返回值 X 输出通道数 X 输出样本数;
这里的输出样本数count是这样计算的:
switch(audio_out.sample_fmt)
{
case AV_SAMPLE_FMT_F32:count = 4;break;
case AV_SAMPLE_FMT_S16:count = 2;break;
case AV_SAMPLE_FMT_U8: count = 1;break;
}
按照如上设置好以后,会发现音频很完整地输出了。
但还存在一个问题,就是延时还是没有设置好,导致部分音乐播放过快,而部分音乐播放缓慢,严重还会出现数堆栈下溢。
下面直接上源码,配置过程参照上次发表的配置,亲测在windows 还有ubuntu里面都能正常播放,另外我用的是Qt5.5 ++
//qtplayer.h
#ifndef QTPLAYER_H
#define QTPLAYER_H
#include <QtMultimedia/QAudio>
#include <QtMultimedia/QAudioFormat>
#include <QtMultimedia/QAudioOutput>
#include <QMutex>
#include <QWaitCondition>
#include <QTest>
#include <QThread>
#ifdef __cplusplus
extern "C"{
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
#include <libavutil/opt.h>
#ifdef __cplusplus
}
#endif
#define OK 1
#define ERROR -1
#define MAX_QUEUE_SIZE 100
#define MAX_AUDIO_FRAME_SIZE 192000
#define MAX_AUDIO_FRAME_SIZE_HIGH 320000
//封装好的音频参数
struct AudioParam{
int64_t channel_layout;
int nb_samples;
int channels;
int sample_rate;
AVSampleFormat sample_fmt;
};
//数据包节点,其实FFmpeg自带了AVPacketList
class PacketNode{
public:
AVPacket pkt;
PacketNode* next;
public:
PacketNode();
};
//包队列
class PacketQueue{
private:
PacketNode* first; //队列头,指向第一个数据包
PacketNode* rear; //队列尾,指向最后一个数据包
int len; //队列长度,即多少个数据包
QMutex mutex; //读写锁
QWaitCondition cond; //读写条件
public:
PacketQueue();
int destroyQueue();
int putPacket(AVPacket* pkt );
int getPacket(AVPacket* pkt );
int initQueue();
int getSize();
};
//音频参数
struct AudioState{
char url[512]; //文件名
AVFormatContext* pFmtCtx; //
AVCodecContext* iAcc;
int audioStream; //音频流索引
SwrContext* pSwrCtx; //音频参数转换上下文
AVFrame* pDecoded; //存放解码后的音频帧
PacketQueue pktList; //
};
//播放线程类
class QtPlayer:public QThread
{
private:
volatile bool stopped;
AudioState* as;
char errStr[128];
public:
QtPlayer();
void getAudioState(AudioState* );
void run();
void stop();
char* getErrStr();
};
//读取数据包线程类
class ReadPacket:public QThread
{
private:
volatile bool stopped;
AudioState* as;
char errStr[128];
public:
ReadPacket();
void getAudioState(AudioState* );
void run();
void stop();
char* getErrStr();
};
AudioState *createAudioState(char* url);
#endif // QTPLAYER_H
源文件:
//readpacket.cpp
#include "qtplayer.h"
ReadPacket::ReadPacket()
{
stopped = false;
}
void ReadPacket::stop()
{
stopped = true;
}
void ReadPacket::getAudioState(AudioState *is)
{
as = is;
}
//读取数据包线程入口
void ReadPacket::run()
{
AVPacket pkt1,*pkt = &pkt1;
AVFormatContext* pFmtCtx = as->pFmtCtx;
//as->pktList.initQueue();
while(1)
{
if(as->pktList.getSize() > MAX_QUEUE_SIZE)
{
QTest::qSleep(10);
continue;
}
if(stopped)
break;
int ret = av_read_frame(pFmtCtx,pkt);
if(ret >=0)
{
//把音频数据包放入队列
if(pkt->stream_index == as->audioStream)
{
as->pktList.putPacket(pkt);
}
else
{
av_packet_unref(pkt);
}
}
else
{
strcpy(errStr,"Reading packet error or end of file.\n");
break;
}
//av_packet_unref(&pkt);
}
}
char* ReadPacket::getErrStr()
{
return errStr;
}
//qtplayer.cpp
#include "qtplayer.h"//
PacketNode::PacketNode()
{
next = NULL;
}
//
PacketQueue::PacketQueue()
{
first = rear = NULL;
len = 0;
}
//初始化队列,还要问我为什么跟构造函数一样
int PacketQueue::initQueue()
{
first = rear = NULL;
len = 0;
return OK;
}
//销毁队列
int PacketQueue::destroyQueue()
{
PacketNode* tmp;
while(first != rear)
{
tmp =first;
first = first->next;
delete tmp;
}
delete first;
len = 0;
return OK;
}
//数据包放入队列
int PacketQueue::putPacket(AVPacket* pkt)
{
PacketNode* node = new PacketNode;
if(!node)
return false;
node->pkt = *pkt;
//当把数据包放入队列的时候需要锁定队列
mutex.lock();
if(!rear)
first = node;
else
rear->next = node;
rear = node;
len++;
//执行完进队操作需要唤醒读包线程
cond.wakeOne();
mutex.unlock();
return OK;
}
//从队列中读取 数据包
int PacketQueue::getPacket(AVPacket *pkt)
{
PacketNode* node;
int ret = 0;
if(first == rear)
return false;
//锁定队列
mutex.lock();
while(1)
{
node = first;
if(node)
{
first = node->next;
if(!first)
rear = NULL;
len--;
*pkt = node->pkt;
delete node;
ret = OK;
break;
}
else
{
//等待读取数据包
cond.wait(&mutex);
}
}
mutex.unlock();
return ret;
}
//用稍微安全的方法获取队列包长度
int PacketQueue::getSize()
{
return len;
}
//打开文件及打开解码器的操作都被封装到这里面,若有一步操作失败,返回NULL,可以看到这个函数可能造成内存泄漏
//但考虑到不大的应用范围,这种程度的泄漏对系统的危害还是比较小的
AudioState *createAudioState(char *url)
{
AudioState* is = new AudioState;
AVCodecContext* iAcc;
if(!is)
return NULL;
AVFormatContext* pFmtCtx = NULL;
strcpy(is->url,url);
if(avformat_open_input(&pFmtCtx,url,NULL,NULL) < 0)
{
return NULL;
}
is->audioStream = -1;
is->pFmtCtx = pFmtCtx;
if(avformat_find_stream_info(pFmtCtx,NULL) < 0)
{
return NULL;
}
for(int i=0;i< pFmtCtx->nb_streams;i++)
{
if(AVMEDIA_TYPE_AUDIO == pFmtCtx->streams[i]->codec->codec_type)
{
is->audioStream = i;
break;
}
}
if(-1 == is->audioStream)
{
return NULL;
}
iAcc = pFmtCtx->streams[is->audioStream]->codec;
is->iAcc = iAcc;
AVCodec* aCodec;
aCodec = avcodec_find_decoder(iAcc->codec_id);
if(!aCodec)
{
return NULL;
}
if(avcodec_open2(iAcc,aCodec,NULL) < 0)
{
return NULL;
}
// is->pDecoded = av_frame_alloc();
if(!is->pDecoded)
{
return NULL;
}
//初始化队列
is->pktList.initQueue();
return is;
}
//
QtPlayer::QtPlayer()
{
stopped = false;
}
//
void QtPlayer::getAudioState(AudioState *is)
{
as = is;
}
//
void QtPlayer::stop()
{
stopped = true;
}
//线程入口,很纳闷QT里面的线程函数居然没有返回值
void QtPlayer::run()
{
//打开音频设备,播放的四个必要参数
AudioParam audio_out;
QAudioFormat format;
QAudioOutput* audio;
QAudioDeviceInfo info;
QIODevice* out;
AVPacket pkt1,*pkt = &pkt1;
info = QAudioDeviceInfo(QAudioDeviceInfo::defaultOutputDevice());
//int bit_rate = as->iAcc->bit_rate;
//设置最大缓冲区大小
int out_size = MAX_AUDIO_FRAME_SIZE_HIGH*2;
uint8_t* play_buf = (uint8_t*)av_malloc(out_size);
//
audio_out.sample_fmt = AV_SAMPLE_FMT_S16;
audio_out.nb_samples = as->iAcc->frame_size;
audio_out.channel_layout = AV_CH_LAYOUT_STEREO;
audio_out.sample_rate = 44100;
audio_out.channels = av_get_channel_layout_nb_channels(audio_out.channel_layout);
//初始化QAudioFormat
format.setSampleRate(audio_out.sample_rate);
format.setChannelCount(audio_out.channels);
format.setCodec("audio/pcm");
format.setSampleType(QAudioFormat::SignedInt);
format.setSampleSize(16);
format.setByteOrder(QAudioFormat::LittleEndian);
if(!info.isFormatSupported(format))
{
fprintf(stderr,"Not a supported audio format.\n");
return ;
}
audio = new QAudioOutput(format);
as->pSwrCtx = swr_alloc_set_opts(NULL,audio_out.channel_layout,audio_out.sample_fmt,audio_out.sample_rate,
as->iAcc->channel_layout,as->iAcc->sample_fmt,as->iAcc->sample_rate,0,NULL);
if(!as->pSwrCtx)
{
fprintf(stderr,"Could not set options for resample context.\n");
return ;
}
audio->setBufferSize(out_size);
out = audio->start();
swr_init(as->pSwrCtx);
//av_init_packet(&pkt);
int frameFinished = 0;
while(1)
{
if(as->pktList.getSize() ==0)
{
QTest::qSleep(10);
continue;
}
if(stopped)
{
break;
}
//从这里获取音频数据包
if(as->pktList.getPacket(pkt))
{
uint8_t* pktdata = pkt->data;
int pktsize = pkt->size;
//循环解码一个数据包
while(pktsize > 0)
{
int ret = avcodec_decode_audio4(as->iAcc,as->pDecoded,&frameFinished,pkt);
if(ret < 0)
{
strcpy(errStr,"Error while decoding packet.\n");
return ;
}
if(frameFinished)
{
ret = swr_convert(as->pSwrCtx,&play_buf,out_size,(const uint8_t**)as->pDecoded->data,as->pDecoded->nb_samples);
//注意这里发生了变动,out_size = 转换上下文返回值 *输出通道数 *输出样本数;
//输出样本数如果是S16对应2,其他的不一定是1,后面那个是瞎写的
//设置好out_size以后,播放就流畅了
out_size = ret * audio_out.channels * (AV_SAMPLE_FMT_S16==audio_out.sample_fmt?2:1);
//write方法会使音频设备不停播放play_buf里面的数据,不管里面有没有数据
out->write((char*)play_buf,out_size);
//这个延时还是不会设,用统一的延时播放有些音频会过快,而有些比较卡,设大了可能出现数据下溢
QTest::qSleep(30);
}
pktdata += ret;
pktsize -= ret;
}
//av_packet_unref(&pkt);
//delete node;
}
else
{
strcpy(errStr,"Could get packet.\n");
break;
}
}
}
char* QtPlayer::getErrStr()
{
return errStr;
}
#include "qtplayer.h"int main(int argc, char *argv[])
{
//注册所有编解码器
av_register_all();
avcodec_register_all();
AudioState* is = createAudioState(argv[1]);
if(!is)
{
fprintf(stderr,"Error create audio state.\n");
return -1;
}
QtPlayer player;
ReadPacket readPkt;
player.getAudioState(is);
readPkt.getAudioState(is);
readPkt.start();
player.start();
// while(1)
{
QTest::qSleep(10);
//printf("%s",player.getErrStr());
//printf("%s",readPkt.getErrStr());
}
return 0;
}