Qt绘制音频波纹图

本文实现Qt绘制FFmpeg解码音频的波纹.

绘制流程为:

  • 音频解码
  • 绘图

解码部分

首先创建一个空白带UI的Qt工程。

在UI上添加一个按钮

ui

对应的click函数中添加解码代码,参考

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
void MainWindow::on_btnPlay_clicked()
{
AVFormatContext *pFormatCtx=avformat_alloc_context();
size_t i;
int audioStream=-1;
AVCodecContext *pCodecCtx=NULL;
AVCodec *pCodec=NULL;
AVCodecParameters *pCodecPara=NULL;
AVPacket *packet=av_packet_alloc(); // 分配AVPacket结构体
AVFrame *pFrame=av_frame_alloc();
char filename[]="/home/jackey/Music/test.mp3";

//支持网络流
avformat_network_init();
//Open
if(avformat_open_input(&pFormatCtx,filename,NULL,NULL)!=0){
printf("Couldn't open file.\n");
return;
}

// Retrieve stream information
if(avformat_find_stream_info(pFormatCtx,NULL)<0){
printf("Couldn't find stream information.\n");
return;
}
// Dump valid information onto standard error
av_dump_format(pFormatCtx, 0, filename, false);

// Find the first audio stream
for(i=0; i < pFormatCtx->nb_streams; i++){
if(pFormatCtx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_AUDIO){
audioStream=i;
break;
}
}

if(audioStream==-1){
printf("Didn't find a audio stream.\n");
return;
}

// Find the decoder for the audio stream
pCodecPara = pFormatCtx->streams[audioStream]->codecpar;
pCodec=avcodec_find_decoder(pCodecPara->codec_id);
if(pCodec==NULL){
printf("Codec not found.\n");
return;
}

//创建解码器
pCodecCtx = avcodec_alloc_context3(pCodec);
if(avcodec_parameters_to_context(pCodecCtx,pCodecPara)<0){
printf("Cannot alloc context.");
return;
}

// Open codec
if(avcodec_open2(pCodecCtx, pCodec,NULL)<0){
printf("Could not open codec.\n");
return;
}

printf("Bitrate: %3ld\n", pFormatCtx->bit_rate);
printf("Codec Name: %s\n", pCodecCtx->codec->long_name);
printf("Channels: %d \n", pCodecCtx->channels);
printf("Sample per Second %d \n", pCodecCtx->sample_rate);
pCodecCtx->channels = 1;
while(av_read_frame(pFormatCtx, packet)>=0){
if(packet->stream_index==audioStream){
if(avcodec_send_packet(pCodecCtx,packet)>=0){
while(avcodec_receive_frame(pCodecCtx,pFrame)>=0){
uint8_t *ptr =pFrame->data[0];
for (int i = 0; i < pFrame->linesize[0]; i += 1024)
{
vdata[length] = ptr[i];
length++;
}
}
}
}
av_packet_unref(packet);
}
// Close the codec
avcodec_close(pCodecCtx);
// Close the video file
avformat_close_input(&pFormatCtx);
av_packet_free(&packet);
av_frame_free(&pFrame);
drawWave();
return ;
}

问题在于如何进行采样,一般的MP3格式的文件采样率为44.1KHz,即每秒采集44100个点作为MP3格式文件的新数据。普通的音乐三分钟,那么就是3 * 60 * 44100 = 7938000,普通的屏幕为1080P,即1920 * 1080 。肯定显示不了那么多点。

那么我们就对于解码的每一帧数据,每1024个点取一个值。

绘图部分

如果直接绘在软件界面,数据量非常大,没有那么大的界面。所以我们将其绘制在QImage上,然后缩放显示在界面上。

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
void MainWindow::drawWave()
{
QPainter painter(&image);
painter.setPen(QPen(Qt::green));
int index = 0;
int length = this->length;
if (length>0)
{
int winterval = ceil(length/image.width()+0.5);
int halfHeight = image.height()>>1;
int hinterval = 600/halfHeight;
int indexWave = 0;
int yValue = 0;
int yValue2 = 0;
int stepIndex = 0;
for (index = 0; index<length ; index+=winterval){
int value = 0;
for (stepIndex = 1; stepIndex < winterval; stepIndex++){
value++;
yValue = vdata[indexWave*winterval+stepIndex];
yValue2 = vdata[indexWave*winterval+stepIndex+1];
yValue = yValue/hinterval;
yValue2 = yValue2/hinterval;
if (yValue<0){
yValue = halfHeight+abs(yValue);
}else{
yValue = halfHeight-yValue;
}

if (yValue2<0){
yValue2 = halfHeight+abs(yValue2);
}else{
yValue2 = halfHeight-yValue2;
}
painter.drawLine(indexWave,yValue,indexWave,yValue2);
}
indexWave++;
}
}
image.save("1","png");
}

显示部分

将图片绘制在图片上后,就要把图片显示在图片上。

1
2
3
4
5
6
void MainWindow::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.drawImage(0,0,image);
update();
}

编译、运行

default

点击播放按钮,会自动解码并绘图

play

绘图的QImage大小为3000 * 600 ,窗口大小为800 * 600 。将窗口像右拉会看见没有显示的部分。

同时在程序所在文件夹保存了一个完整的图片结果。

result

完整的Qt工程在QtApps下的pcmwave中。