赞
踩
写在前面:
这部分对应视频课程中的5-1~5-8。实现了播放器的音频播放功能。前面的课程教了如何播放视频,但对音频没有处理。
需要播放音频的话,要生成一个对象QAudioOutput *out同时要包含它的头文件
#include <QAudioOutput>,如何设定音频的参数?那么就需要在设置一个QAudioFormat fmt;通过它来指定音频输出的格式。
fmt.setSampleRate(48000) :表示一秒钟采集48000个音频数据,越大效果越好。fmt.setCodec("audio/pcm") :表示音频格式,这里的pcm格式是未压缩普通格式。
调用out = new QAudioOutput(fmt);创建输出流。打开音频输出之后,
之后启动,调用start函数,回返回一个QIODevice 的接口。QIODevice *ad out->start();通过QIODevice 接口就可以向里面写入东西播放音频了ad->write(***);
首先将构造函数放进protected中,这样做的目的是音频只有一个播放类,所以使用protected,这样就不能随便创建XAudioPlay();构造函数了。只能由内部自己创建(单件模式)
使用一种方法,隐藏一些信息,把类分装完成之后,使得调用接口的人不知道这个函数是如何实现的,这样将来在换其他方式音频播放时候,调用者就不需要在关注这些变化。所以将这些函数定义成纯虚函数,在继承类中实现这些接口。
这样的话,我们就将CXAudioPlay做成了继承类。这样将来实现的方法就放在了继承类里面。CXAudioPlay则是我们的接口类就不用实现这些函数功能了。
如果没有完全实现纯虚函数,就会变成一个抽象类,抽象类是不能实例化的,也就是不能实现对象。
- 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 ~XAudioPlay();
- protected:
- XAudioPlay();
- };
-
- class CXAudioPlay : public XAudioPlay
- {
- public:
- bool Start()
- {
- return true;
- }
- void Play(bool isplay)
- {
-
- }
- bool Write(const char *data, int datasize)
- {
- return true;
- }
- };
启动的实现,流程大致是设置一个QAudioFormat参数,然后传入参数并start,start之后会返回一个QIODevice。
每次重新打开一个视频的时候,需要stop,清除output。
- void Stop()
- {
- if (output)
- {
- output->stop();
- delete output;
- output = NULL;
- }
- }
- void Play(bool isplay)
- {
- if (!output) return;
- if (isplay)
- {
- output->resume();//恢复播放
- }
- else
- {
- output->suspend();//暂停
- }
- }
write函数的作用,由文件当中读取出音频数据来,在放到QAudio进行播放,但是播放是从缓冲区里面去读的,所以要判断缓冲区的大小。
- int GetFree()
- {
- if (!output) return 0;
- return output->bytesFree();//获取是否有足够的空间
- }
-
- bool Write(const char *data, int datasize)
- {
- if (io)
- io->write(data, datasize);
- return true;
- }
对音频进行打开解码器,进行解码,重采样三步操作。
- 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 false;
- }
- this->sampleRate = enc->sample_rate;
- this->channel = enc->channels;
- switch (enc->sample_fmt)
- {
- case AV_SAMPLE_FMT_S16:
- this->sampleSize = 16;
- break;
- case AV_SAMPLE_FMT_S32:
- this->sampleSize = 32;
- break;
- default:
- break;
- }
- printf("audio sample rate:%d sample size %d sample channel %d\n",
- this->sampleRate, this->sampleSize, this->channel);
- }
在ffmpeg老版本中的视频解码音频解码是用不同函数的,新版的则是使用同一个函数。但是这里会存在一个问题,视频和音频的解码后的数据存放在哪里?肯定不能存放在同个地方。因为视频和音频是两个不同线程,而且要同步播放,用同块内存肯定要出问题的。所以要给音频增加一个内存。
AVFrame *pcm = NULL;
加入音频之后还带来一个问题,我们在控制播放进度的时候,以前是以视频为准的 ,控制视频的播放速度,但音频则不行,会导致声音失真。那么之前的pts是存放视频的,现在就要改成存放音频的pts。同时改写之前的AVFrame *Decode(const AVPacket *pkt);函数让它直接返回pts音频解码之后可以直接播放,但更多时候,解码出来的音频格式并不一定满足当前机器播放的要求。这里需要重采样。例如32位改成16位的或者是通道数2改1.
引用头文件#include <libswresample/swresample.h>
再引用重采样的库文件#pragma comment(lib,"swresample.lib")。
音频解码完之后,如果同段声音放两次会出现问题,所以要保证解码函数和重采样函数在同个线程就好了。
SwrContext *aCtx = NULL; //创建音频转码器的容器- struct SwrContext *swr_alloc_set_opts //设置属性
- (struct SwrContext *s, //
- int64_t out_ch_layout, //表示输出这个通道类型,比如2.1声道、5.1声道这种
- enum AVSampleFormat out_sample_fmt, //表示输出的采样大小,
- int out_sample_rate, //表示输出的采样率
- int64_t in_ch_layout, //表示输入的通道类型
- enum AVSampleFormat in_sample_fmt, //表示输入的采样大小
- int in_sample_rate, //表示输入的采样率
- int log_offset, //表示偏移
- void *log_ctx //暂时不了解
- );
ffmpeg的音频处理申请空间函数struct SwrContext *swr_alloc(void);;
ffmpeg的音频处理释放空间函数void swr_free(struct SwrContext **s);;
- int swr_convert( //设置输出空间
- struct SwrContext *s, //打开的重采样设置
- uint8_t **out, //输出的地方
- int out_count, //输出的采样数量的大小,要保证足够大
- const uint8_t **in , //输入的数据
- int in_count //输入的数据大小
- );
- //返回值:每一个通道的样本数量,如果小于0就是失败了。
外部调用该函数的地方是想知道重采样了多少字节使用av_samples_get_buffer_size函数实现
- int av_samples_get_buffer_size(
- int *linesize,
- int nb_channels, //样本通道
- int nb_samples, //样本数量
- enum AVSampleFormat sample_fmt, //样本的类型
- int align //
- );
- 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;
- }
首先记得对音频处理函数做互斥。
音频读取速率也需要控制,也不能够一直读取音频数据来解码。
音频缓冲区:音频在播放的时候会不断从缓冲区里面读取,读一帧在删除一帧,如果空间满的时候,就没办法写入了,这样就会造成声音的失真。
- int free = XAudioPlay::Get()->GetFree();//读取音频缓冲区的剩余空间
- if (free < 10000) //空间不足的话 就不要往缓冲区里面写数据了。
- {
- msleep(1);
- continue;
- }
- if (pkt.stream_index == XFFmpeg::Get()->audioStream)//如果是音频流,
- {
- XFFmpeg::Get()->Decode(&pkt);//解码音频,Decode内部做了音视频的判断
- av_packet_unref(&pkt);//释放空间
- int len = XFFmpeg::Get()->ToPCM(acm_out);//重采样
- XAudioPlay::Get()->Write(acm_out,len); //播放音频
- continue;
- }
目前音视频播放都挺正常的,但是如果播放网络流视频,比如rtsp、rtmp这类网络流的,就很有可能造成不同步现象,
解决方法:现将视频缓存起来,判断视频和音频是否同步,不同步就等一下再播放。
使用std的list来缓存视频。
using namespace std;
static list<AVPacket> videos;
这里不能直接拿音频和视频的pts做比较,因为他们两个的timebase不同,所以必须都转换成毫秒才能进行对比。- videos.push_back(pkt); //入栈
- videos.front();//出栈
- while (videos.size() > 0)
- {
- AVPacket pack = videos.front();//出栈
- int pts = XFFmpeg::Get()->GetPts(&pack);//获取视频的毫秒数
- if (pts > apts) //当视频播放进度小于音频的时候才解码
- {
- break;
- }
- XFFmpeg::Get()->Decode(&pack);
- av_packet_unref(&pack);//释放空间
- videos.pop_front();
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。