• FFmpeg5开发入门教程17:软件解码音频并使用QAudioOutput播放


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

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

    第一部分:解码

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

    第二部分:QAudioOutput播放

    首先就是播放的PCM格式:

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

    然后设置播放类QAudioOutput:

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

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

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

    streamOut->write();
    
    • 1

    将数据写入播放设备。

    完整代码

    #include 
    
    using namespace std;
    
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    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;
    }
    
    • 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

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

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

    延时是多少?

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

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

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

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

    完整代码在FFmpeg_beginner中的17.audio_player_decode_by_ffmpeg_play_by_qt

    本文首发于:FFmpeg5开发入门教程17:软件解码音频并使用QAudioOutput播放

  • 相关阅读:
    阶乘分解质因数
    基于虚拟力优化的无线传感器网络覆盖率matlab仿真
    如何获取mysql数据库中所有数据类型
    CSS特殊学习网址
    第61章 Jquery JSON Table EntityFrameworkCore自动生成数据库
    charles安装及配置(包含安卓7.0以下及安卓7.0以上配置)
    性能优化之懒加载 - 基于观察者模式和单例模式的实现
    Go 之 captcha 生成图像验证码
    一行代码解决Three.js中只能在一侧看到物体的问题
    六、vpp 流表+负载均衡
  • 原文地址:https://blog.csdn.net/qq_26056015/article/details/126094643