本文实现Qt绘制FFmpeg解码音频的波纹.
绘制流程为:
解码部分首先创建一个空白带UI的Qt工程。
在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 (); AVFrame *pFrame=av_frame_alloc (); char filename[]="/home/jackey/Music/test.mp3" ; avformat_network_init (); if (avformat_open_input (&pFormatCtx,filename,NULL ,NULL )!=0 ){ printf ("Couldn't open file.\n" ); return ; } if (avformat_find_stream_info (pFormatCtx,NULL )<0 ){ printf ("Couldn't find stream information.\n" ); return ; } av_dump_format (pFormatCtx, 0 , filename, false ); 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 ; } 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 ; } 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); } avcodec_close (pCodecCtx); 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 (); }
编译、运行
点击播放按钮,会自动解码并绘图
绘图的QImage大小为3000 * 600 ,窗口大小为800 * 600 。将窗口像右拉会看见没有显示的部分。
同时在程序所在文件夹保存了一个完整的图片结果。