BPlay1.0系列(6:视频播放) 已经完成了媒体文件的视频数据播放功能,但是播放时速度(帧率)无法控制并且是没有声音的,这就需要实现音频播放的功能来解决这两个问题。
1、音频解码
音频解码和视频解码流程上有很大的相似,都是将解封装后的流数据进行解码,音视频主控线程Bffmpeg::run()里面根据流索引将对应的流数据加入音频流队列后,音频解码/播放线程Baudio::run()就可以取到数据并进行相关处理(解码),此处可以复用视频解码里面的Bffmpeg::Decode(&pkt)方法:
/********************************
* void BAudio::run()
* 功能:音频解码/播放线程
* *****************************/
void Baudio::run()
{
while (audiorun)
{
AVPacket pkt;
memset(&pkt , 0, sizeof(pkt));
if (Bffmpeg::GetInstance()->GetAudioQue().que.size() == 0) {
msleep(1);
continue;
} else {
/* 音频流编码数据出队列 */
QMutexLocker Locker(&Bffmpeg::GetInstance()->GetAudioQue().mtx);
pkt = Bffmpeg::GetInstance()->GetAudioQue().que.front();
Bffmpeg::GetInstance()->GetAudioQue().que.pop_front();
}
/* 解码 */
AVFrame* frame = Bffmpeg::GetInstance()->Decode(&pkt);
av_packet_unref(&pkt);
if (frame == NULL) {
continue;
}
}
return;
}
2、重采样
在得到音频解码数据(PCM)之后,需要将其送到声卡才可播放,但是如果PCM的格式和声卡的配置参数不一样时,就会出现声音异常的现象,所以需要将两者的信息进行一次同步,这个同步的过程就是重采样。
采样率、样本大小和通道数是音频最关键的三个参数,重采样主要就是针对这三个参数进行了一次音频数据重新构建,知道达到声卡可以识别的格式。
首先需要为BPlay设置声卡的配置参数,这块直接使用QT的QAudioOutput类(音频播放),而此步放在媒体文件加载完成后(Bffmpeg::BLoadMediaFile(QString FilePath))进行即可:
/********************************
* void Baudio::OpenAudioOutput()
* 功能:初始化设置音频设备参数
* *****************************/
void Baudio::OpenAudioOutput()
{
QAudioFormat fmt; // 设置音频输出格式
fmt.setSampleRate(44100); // 1秒的音频采样率
fmt.setSampleSize(16); // 声音样本的大小
fmt.setChannelCount(Bffmpeg::GetInstance()->GetFormatContext()->streams[Bffmpeg::GetInstance()->GetAudioIndex()]->codec->channels); // 声道
fmt.setCodec("audio/pcm"); // 解码格式
fmt.setByteOrder(QAudioFormat::LittleEndian);
fmt.setSampleType(QAudioFormat::UnSignedInt); // 设置音频类型
output = new QAudioOutput(fmt);
io = output->start(); // 播放开始
return;
}
此处将采样率设置成了44100,样本大小为16bit,声道不变,接着在音频数据解码完成后将参数设置与其对应上即可。设置重采样参数:
Swr = swr_alloc();
AVCodecContext *AudioCodec = FormatContext->streams[Audio_index]->codec;
swr_alloc_set_opts(Swr, AudioCodec->channel_layout, AV_SAMPLE_FMT_S16, 44100, /* 目标格式 */
AudioCodec->channel_layout, AudioCodec->sample_fmt, AudioCodec->sample_rate, 0, 0); /* 源格式 */
swr_init(Swr);
对音频码流重采样并且播放:
/********************************
* void BAudio::run()
* 功能:音频解码/播放线程
* *****************************/
void Baudio::run()
{
char out[20000] = { 0 };
while (audiorun)
{
AVPacket pkt;
memset(&pkt , 0, sizeof(pkt));
BLOG("output->bytesFree()[%d], output->periodSize()[%d]", output->bytesFree(), output->periodSize());
if ((Bffmpeg::GetInstance()->GetAudioQue().que.size() == 0) || (output->bytesFree() < output->periodSize())) {
msleep(1);
continue;
} else {
/* 音频流编码数据出队列 */
QMutexLocker Locker(&Bffmpeg::GetInstance()->GetAudioQue().mtx);
pkt = Bffmpeg::GetInstance()->GetAudioQue().que.front();
Bffmpeg::GetInstance()->GetAudioQue().que.pop_front();
}
/* 解码 */
AVFrame* frame = Bffmpeg::GetInstance()->Decode(&pkt);
av_packet_unref(&pkt);
if (frame == NULL) {
continue;
}
/* 音频重采样 */
AVFormatContext *FormatContext = Bffmpeg::GetInstance()->GetFormatContext();
AVCodecContext *CodecContext = FormatContext->streams[Bffmpeg::GetInstance()->GetAudioIndex()]->codec;
uint8_t *data[1];
data[0] = (uint8_t *)out;
int len = swr_convert(Bffmpeg::GetInstance()->GetSwrContext(), data, sizeof(out), (const uint8_t **)frame->data, frame->nb_samples);
if (len <= 0)
{
return;
}
int datasize = av_samples_get_buffer_size(NULL, CodecContext->channels, frame->nb_samples, AV_SAMPLE_FMT_S16, 0);
/* 播放 */
io->write(out, datasize);
}
return;
}
此时音频流数据就可以正常播放了。