索引地址:系列教程索引地址
上一篇:FFmpeg4入门16:音频重采样解码为pcm
上一篇的FFmpeg4入门16:音频重采样解码为pcm介绍了解码音频并将数据格式由float变为signed int(双声道、16位数据、44100Hz、小端数据这些保持不变),然后将数据保持为PCM文件,并使用ffplay播放测试。
本篇使用Qt的QAudioOutput类来播放解码后的数据,省略了保存为文件然后使用别的程序播放这个过程。
第一部分:解码
直接把上一篇的FFmpeg4入门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();
|
设置音量,并开始将数据写入硬件,当然此时解码还没有开始,没有数据可以播放。
如果获取到了解码后的数据就使用:
将数据写入播放设备。
完整代码
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
| #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);
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; }
int out_size = av_samples_get_buffer_size(0, out_channels, len, out_sample_fmt, 1);
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); } } } } } 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
。
下一篇:FFmpeg4入门18:解码内存数据并播放