用Qt线程及媒体类播放FFmpeg解码的音频数据

接着上一次的工程,这次把读取音频包跟解码音频包放到不同的线程里面去。而且找到了播放卡顿的原因,按照网上大神的说法,就是输出缓冲区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;
}

 

转载于:https://my.oschina.net/phoromeon/blog/672045

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值