Qt绘制音频波纹图-QWT方法

在上一篇Qt绘制音频波纹图中,我们使用Qt/FFmpeg绘制了简单的音频波纹图,本文将使用Qt的qwt库绘制音频解码的所有点。

本文的主要目的,是测试Qt/qwt的绘图最大能力。

测试配置为:

1
2
3
4
5
6
7
8
9
Manjaro Linux stable
Linux kernel 5.15.2
CPU AMD 5950 16 Core 32 Thread
金士顿 32G
GPU GeForce GTS 450
显示器 Redmi 27英寸 RMMNT27NF 1080P 16:9 GTG6ms 60-75Hz
qwt 6.2.0
Qt 5.15.2
GCC 11.1.0

Qt paintevent刷新频率为
QWT刷新频率为:

各个组件的刷新频率同步很重要,如果显示器频率为60Hz,那么就算软件刷新为5Hz,也需要60ms来刷新显示。

MindViewer-TGAM模块数据图形化软件中,我们简单使用过QWT库。

解码

就是把之前的代码拿过来

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

void FFmpegAudio::run()
{
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;
}
pCodecCtx->pkt_timebase = pFormatCtx->streams[audioStream]->time_base;

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

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 ++)
{
vdataL.append(ptr[i]);
vdataR.append(-ptr[i]);
}
}
msleep(17);
emit updateFrame(vdataL,vdataR);
vdataL.clear();
vdataR.clear();
}
}
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);

printf("Decode done.\n");
}

显示

初始化界面

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
maxCnt=5000;//x轴坐标的数量0-1000
//设置x轴坐标值
for(int i=0;i<maxCnt;i++){
xdata.append(i);
}

canvas = new QwtPlotCanvas(this);
canvas->setPalette(Qt::white);//背景色为白色
canvas->setBorderRadius(10);//画面边框角为半径为10的圆角
setCanvas(canvas);
plotLayout()->setAlignCanvasToScales(true);//设置坐标轴数值自适应

//坐标与图示说明
setAxisTitle(QwtPlot::yLeft,"Value");
setAxisTitle(QwtPlot::xBottom,"time: s");
setAxisScale(QwtPlot::yLeft,-254,255);
setAxisScale(QwtPlot::xBottom,0,maxCnt);

//画板中的网格图
QwtPlotGrid *grid = new QwtPlotGrid();
grid->enableX(true);
grid->enableY(true);
grid->setMajorPen(Qt::black,0,Qt::DotLine);
grid->attach(this);
//画板中的折线
curveChannelLeft = new QwtPlotCurve("left");
curveChannelLeft->setPen(Qt::red,2);
curveChannelLeft->setRenderHint(QwtPlotItem::RenderAntialiased,true);

curveChannelRight = new QwtPlotCurve("right");
curveChannelRight->setPen(Qt::green,2);
curveChannelRight->setRenderHint(QwtPlotItem::RenderAntialiased,true);

QwtPlotItemList items=itemList(QwtPlotItem::Rtti_PlotCurve);
for ( int i = 0; i < items.size(); i++ ){
items[i]->setVisible(true);
}

replot();
setAutoReplot( true );//设置自动重画,相当于更新

接收到数据后,显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if(channelDataL.size()>=maxCnt){
// channelDataL.pop_front();
channelDataL.remove(0,4086);
}
if(channelDataR.size()>=maxCnt){
// channelDataR.pop_front();
channelDataR.remove(0,4086);
}
channelDataL.append(dl);
channelDataR.append(dr);

curveChannelLeft->setSamples(xdata,channelDataL);
curveChannelLeft->attach(this);
curveChannelLeft->setLegendAttribute(curveChannelLeft->LegendShowLine);//显示图例的标志,这里显示线的颜色。

curveChannelRight->setSamples(xdata,channelDataR);
curveChannelRight->attach(this);
curveChannelRight->setLegendAttribute(curveChannelLeft->LegendShowLine);//显示图例的标志,这里显示线的颜色。

replot();

然后绑定信号槽

1
2
ffAudio = new FFmpegAudio;
connect(ffAudio,&FFmpegAudio::updateFrame,ui->frame,&WaveCurve::updateChannelData);

使用的音频文件信息如下

1
2
3
4
5
6
7
8
9
10
11
12
13
Input #0, mp3, from '/home/jackey/Music/test.mp3':
Metadata:
artist : 龙千玉
title : 阿郎
album : 闽南情缘
track : 8
encoder : Lavf56.4.101
comment : 163 key(Don't modify):L64FU3W4YxX3ZFTmbZ+8/RVLtWbDWHLNAhEGfl+Nf7pQiiGPszFV2YCIc7+l7fMpf4dVB11wt7woihWFgSEOPD0dL3MYVy6tLTECRlW+tDT92DXQ+WZfwIbT6ars1WfT0+Z4TY2HzdBJJaYYm/FUvM9ws7oMCSJpzl4s7bTtDo++fyxsnqTH2CRKf0jctmKyrObJZu+fT1ba6aomgqYAmyX5FbZQ5p26agom+DDI6
Duration: 00:04:28.83, start: 0.025056, bitrate: 327 kb/s
Stream #0:0: Audio: mp3, 44100 Hz, stereo, fltp, 320 kb/s
Stream #0:1: Video: png, rgba(pc), 414x414, 90k tbr, 90k tbn, 90k tbc (attached pic)
Metadata:
comment : Cover (front)

时长为4分28秒,即269秒。

共有10291帧,每帧有4608个数据,总计47420928个点。平均每秒显示176286个点。1毫秒是176个点。

解码一帧获得4608个点,每帧延时25毫秒,那么一秒就是解码40帧,共计184320个点。两通道就是368640个点

绘图效果为:result1

CPU占用率为:htop

将延时调整为17毫秒,即60hz。一秒解码60帧,共计276480个点,两个通道为552960个点

效果为:htop

CPU占用率为:htop

完整代码在QtApps中的pcmwave2


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!