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);
}

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

然后设置播放类QAudioOutput:

audioOutput = new QAudioOutput(audioFmt);
audioOutput->setVolume(100);

streamOut = audioOutput->start();

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

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

streamOut->write();

将数据写入播放设备。

完整代码

#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中的17.audio_player_decode_by_ffmpeg_play_by_qt

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

猜你喜欢

转载自blog.csdn.net/qq_26056015/article/details/126094643