FFmpeg5入门教程10.16:软件解码音频并使用QAudioOutput播放

索引地址:系列教程索引地址

上一篇:FFmpeg5入门教程10.15:音频重采样解码为pcm

上一篇的FFmpeg5入门教程10.15:音频重采样解码为pcm介绍了解码音频并将数据格式由float变为signed int(双声道、16位数据、44100Hz、小端数据这些保持不变),然后将数据保持为PCM文件,并使用ffplay播放测试。

本篇使用Qt的QAudioOutput类来播放解码后的数据,省略了保存为文件然后使用别的程序播放这个过程。

第一部分:解码

直接把上一篇的FFmpeg5入门教程10.15:音频重采样解码为pcm的代码拿过来就可以了。

第二部分:QAudioOutput播放

首先就是播放的PCM格式:

1
2
3
4
5
6
7
8
9
10
11
12
QAudioFormat audioFmt;
audioFmt.setSampleRate(44100);
audioFmt.setChannelCount(2);
audioFmt.setSampleSize(16);
audioFmt.setCodec("audio/pcm");
audioFmt.setByteOrder(QAudioFormat::LittleEndian);
audioFmt.setSampleType(QAudioFormat::SignedInt);

QAudioDeviceInfo info = QAudioDeviceInfo::defaultOutputDevice();
if(!info.isFormatSupported(audioFmt)){
audioFmt = info.nearestFormat(audioFmt);
}

设置基本的参数,比如44100Hz,2通道,16位数据,小端数据。

然后设置播放类QAudioOutput:

1
2
3
4
audioOutput = new QAudioOutput(audioFmt);
audioOutput->setVolume(100);

streamOut = audioOutput->start();

设置音量,并开始将数据写入硬件,当然此时解码还没有开始,没有数据可以播放。

如果获取到了解码后的数据就使用:

1
streamOut->write();

将数据写入播放设备。

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#include <iostream>

using namespace std;

#include <QString>
#include <QByteArray>
#include <QDebug>
#include <QFile>
#include <QAudioFormat>
#include <QAudioOutput>
#include <QTimer>
#include <QTest>

extern "C"{
#include "libavcodec/avcodec.h"
#include "libavfilter/avfilter.h"
#include "libavformat/avformat.h"
#include "libavutil/avutil.h"
#include "libavutil/ffversion.h"
#include "libswresample/swresample.h"
#include "libswscale/swscale.h"
#include "libpostproc/postprocess.h"
}

#define MAX_AUDIO_FRAME_SIZE 192000

int main()
{
QString _url="/home/jackey/Music/test.mp3";

QAudioOutput *audioOutput;
QIODevice *streamOut;

QAudioFormat audioFmt;
audioFmt.setSampleRate(44100);
audioFmt.setChannelCount(2);
audioFmt.setSampleSize(16);
audioFmt.setCodec("audio/pcm");
audioFmt.setByteOrder(QAudioFormat::LittleEndian);
audioFmt.setSampleType(QAudioFormat::SignedInt);

QAudioDeviceInfo info = QAudioDeviceInfo::defaultOutputDevice();
if(!info.isFormatSupported(audioFmt)){
audioFmt = info.nearestFormat(audioFmt);
}
audioOutput = new QAudioOutput(audioFmt);
audioOutput->setVolume(100);

streamOut = audioOutput->start();

AVFormatContext *fmtCtx =avformat_alloc_context();
AVCodecContext *codecCtx = NULL;
AVPacket *pkt=av_packet_alloc();
AVFrame *frame = av_frame_alloc();

int aStreamIndex = -1;

do{
if(avformat_open_input(&fmtCtx,_url.toLocal8Bit().data(),NULL,NULL)<0){
qDebug("Cannot open input file.");
return -1;
}
if(avformat_find_stream_info(fmtCtx,NULL)<0){
qDebug("Cannot find any stream in file.");
return -1;
}

av_dump_format(fmtCtx,0,_url.toLocal8Bit().data(),0);

for(size_t i=0;i<fmtCtx->nb_streams;i++){
if(fmtCtx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_AUDIO){
aStreamIndex=(int)i;
break;
}
}
if(aStreamIndex==-1){
qDebug("Cannot find audio stream.");
return -1;
}

AVCodecParameters *aCodecPara = fmtCtx->streams[aStreamIndex]->codecpar;
AVCodec *codec = avcodec_find_decoder(aCodecPara->codec_id);
if(!codec){
qDebug("Cannot find any codec for audio.");
return -1;
}
codecCtx = avcodec_alloc_context3(codec);
if(avcodec_parameters_to_context(codecCtx,aCodecPara)<0){
qDebug("Cannot alloc codec context.");
return -1;
}
codecCtx->pkt_timebase = fmtCtx->streams[aStreamIndex]->time_base;

if(avcodec_open2(codecCtx,codec,NULL)<0){
qDebug("Cannot open audio codec.");
return -1;
}

//设置转码参数
uint64_t out_channel_layout = codecCtx->channel_layout;
enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
int out_sample_rate = codecCtx->sample_rate;
int out_channels = av_get_channel_layout_nb_channels(out_channel_layout);
//printf("out rate : %d , out_channel is: %d\n",out_sample_rate,out_channels);

uint8_t *audio_out_buffer = (uint8_t*)av_malloc(MAX_AUDIO_FRAME_SIZE*2);

SwrContext *swr_ctx = swr_alloc_set_opts(NULL,
out_channel_layout,
out_sample_fmt,
out_sample_rate,
codecCtx->channel_layout,
codecCtx->sample_fmt,
codecCtx->sample_rate,
0,NULL);
swr_init(swr_ctx);

double sleep_time=0;

while(av_read_frame(fmtCtx,pkt)>=0){
if(pkt->stream_index==aStreamIndex){
if(avcodec_send_packet(codecCtx,pkt)>=0){
while(avcodec_receive_frame(codecCtx,frame)>=0){
if(av_sample_fmt_is_planar(codecCtx->sample_fmt)){
int len = swr_convert(swr_ctx,
&audio_out_buffer,
MAX_AUDIO_FRAME_SIZE*2,
(const uint8_t**)frame->data,
frame->nb_samples);
if(len<=0){
continue;
}
//qDebug("convert length is: %d.\n",len);

int out_size = av_samples_get_buffer_size(0,
out_channels,
len,
out_sample_fmt,
1);
//qDebug("buffer size is: %d.",dst_bufsize);

sleep_time=(out_sample_rate*16*2/8)/out_size;

if(audioOutput->bytesFree()<out_size){
QTest::qSleep(sleep_time);
streamOut->write((char*)audio_out_buffer,out_size);
}else {
streamOut->write((char*)audio_out_buffer,out_size);
}
//将数据写入PCM文件
//fwrite(audio_out_buffer,1,dst_bufsize,file);
}
}
}
}
av_packet_unref(pkt);
}
}while(0);

av_frame_free(&frame);
av_packet_free(&pkt);
avcodec_close(codecCtx);
avcodec_free_context(&codecCtx);
avformat_free_context(fmtCtx);

streamOut->close();

return 0;
}

问题有两个,解码两帧间的延时是多少?什么时候该延时?

什么时候该延时呢?通过audioOutput->bytesFree()获取播放缓冲区是否还有数据,如果有就表示此时缓冲区中还有数据没有播放完,就延时等待播放完。

延时是多少?

对于本文参数,一秒钟有44100(字节)* 16(位)* 2(通道)/ 8(位)=176400字节。

而在代码中通过out_size = av_samples_get_buffer_size()获取数据缓冲区中的字节数,那么总数量/out_size=延时数。

编译运行就可以正常播放了。

延时差一点都会导致播放有杂音。

完整代码在ffmpeg_beginner中的10.16.audio_player_decode_by_ffmpeg_play_by_qt

下一篇:FFmpeg5入门教程10.17:解码内存数据并播放


FFmpeg5入门教程10.16:软件解码音频并使用QAudioOutput播放
https://feater.top/ffmpeg/ffmpeg-audio-decode-by-cpu-and-play-by-qaudiooutput/
作者
JackeyLea
发布于
2021年4月7日
许可协议