1. 读取 文件名
avformat_open_input(&fmt_ctx, filePath.toStdString().c_str(),nullptr, nullptr);
2. 读取完整文件流信息
avformat_find_stream_info(fmt_ctx,nullptr);
3. 获取音频流索引
int audioStreamIndex = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, 0 ,nullptr);
4. 获取音频流结构体
AVStream m_audioStream = fmt_ctx->streams[audioStreamIndex];
5. 获取解码器静态指针
const AVCodec* codec = avcodec_find_decoder(fmt_ctx->codecper->codec_id);
6. 获取解码器上下文
decoder_ctx = avcodec_alloc_context3(codec);
7. 复制解码参数
avcodec_parameters_to_context(decoder_ctx, m_audioStream->codecper);
8. 打开解码器
avcodec_open2(decoder_ctx,codec, nullptr);
9. 创建音频帧
AVFrame m_audioFrame = av_frame_alloc();
10. 创建并初始化音频转换器
SwrContext *m_swrContext;
AVChannelLayout *ch_layout;
//int av_channel_layout_from_mask(AVChannelLayout *ch_layout, uint64_t mask);
av_channel_layout_form_mask(ch_layout, AV_CH_LAYOUT_STEREO);
swr_alloc_set_opts2(&m_swrContext
ch_layout, AV_SAMPLE_S16,44100,
&decoder_ctx->ch_layout,
deocder_ctx->sample_fmt,
decoder_ctx->sample_rate,
0, nullptr //日志偏移量 自定义日志上下文
);
swr_init(m_swrContext);
11. 创建音频缓冲区(根据输入的采样率和样本数, 获取输出的样本数以及可能积压的延迟样本数计算做大输出样本数,根据最大输出样本数和采样率计算缓冲区大小,申请缓冲区大小的内存)
int inRate = decoder_ctx->sample_rate;
int inSamples = decoder_ctx->frame_size > 0 ? decoder_ctx->frame_size : 1024;
int outSamples = av_rescle_rnd(inSamples, 44100, inRate, AV_ROUND) *2;
// 按两秒估算
int delaySample = av_rescale_rnd(2 * inRate, 44100, inRate, AV_ROUND);
int maxOutSamples = outSamples + delaySample;
int bufferSize = av_sample_get_buffer_size(nullptr, 2, maxOutSamples, AV_SAMPLE_S16, 0);
uint8_t *m_audioBuffer = (uint8_t*)av_malloc(bufferSize);
12. 创建压缩包
AVPacket* m_packet = av_packet_alloc();
13. 循环解码 (1. 从fmt_ctx读取完整数据包 2. 将数据包发送到解码器 3. 从解码器中读取解码后的音频帧 4. 计算需要输出的样本数,包括swr内延迟的 5. 重采样 6. 将数据发送到主线程)
while(!isInterruptionRequested()){
av_read_frame(fmt_ctx, m_packet);
avcodec_send_packet(decoder_ctx, m_packet);
while(avcodec_receive_frame(decoder_ctx, m_audioFrame) >= 0){
int delay = swr_get_delay(m_swrContext, decoder_ctx->sample_rate);
int outSample = av_rescale_rnd(delay + m_audioFrame->nb_samples, 44100, m_audioFrame->sample_rate, AV_ROUND_UP);
int conver = swr_conver(m_swrContext, &m_audioBuffer, outSample,
(const uint8_t **)m_audioFrame->data, m_audioFrame->nb_samples
);
if(conver > 0){
int outSize = conver * 2 * sizeof(int16_t);
QByteArray pcm((const char*)m_audioBuffer, outSize);
emit sendPcm(pcm);
}
av_frame_unref(m_audioFrame);
}
av_packet_unref(&m_packet)
}
14. 读取解码器中残留的帧(13上述中的 3-6)
int ret = 0;
while(ret >= 0){
ret = av_read_frame(decoder_ctx, m_audioFrame);
if(ret == AVERROR_EOF || ret == AVERROR(EAGAIN))
break;
int delay = swr_get_delay(m_swrContext, m_audioFrame->nb_samples);
int outSamples = av_rescale_rnd(delay + m_audioFrame->nb_samples, 44100, m_audioFrame->sample_rate, AV_ROUND_UP);
int conver = swr_conver(&m_swrContext,
m_audioBuffer, outSamples,
(const uint8_t**)m_audioFrame->data,
m_audioFrame->nb_samples
);
if(conver > 0){
int outSize = conver *2 * sizeof(int16_t);
QByteArray pcm((const char*)m_audioBuffer, outSize);
emit sendPcm(pcm);
}
av_frame_unref(m_audioFrame);
}
av_packet_unref(&m_packet);
15. 释放
void release(){
if(m_swrContext){
swr_free(&m_swrContext);
m_swrContext = nullptr;
}
if(m_audioFrame){
av_frame_free(&m_audioFrame);
m_audioFrame = nullptr;
}
if(m_audioBuffer){
av_freep(&m_audioBuffer);
m_audioBuffer = nullptr;
}
if(decoder_ctx){
avcodec_free_context(&decoder_ctx);
decoder_ctx = nullptr;
}
if(fmt_ctx){
avformat_free_context(&fmt_ctx);
fmt_ctx = nullptr;
}
}
主线程
1. 设置音频格式
QAudioFormat m_audioFormat;
m_audioFormat.setSampleRate(44100);
m_audioFormat.setChannelCount(2);
m_audioFormat.setSampleSize(16);
m_audioFormat.setSampleType(QAudioSample::SignedInt);
m_audioFormat.setByteOrder(QAudioSample::LittleEndian);
2. 设置音频播放设备
QAudioDeviceInfo m_audioDeviceInfo = QAudioDeviceInfo::defaultOutputDevice();
3. 检查设备是否支持,不支持就用最接近的格式
if(!m_audioDeviceInfo.isFormatSupported(m_audioFormat)){
qDebug() << "格式不支持,使用最接近的格式";
m_audioFormat = m_audioDeviceInfo.nearestFormat(m_audioForamt);
qDeubg() << "实际支持格式:" << m_audioFormat.sampleRate() << "Hz"
<< m_audioFormat.sampleSize() << "bit" << m_audioFormat_channelCount() << "ch";
}
4. 创建并设置输出格式,缓冲区大小 创建并初始化设备IO
QAudioOutput *m_audioOutput = new QAudioOutput(m_audioFormat, this);
// 44100Hz * 2ch * 2(字节) * 0.2s
m_audioOutput->setBufferSize(static_cast<int> 44100 *2 *2 *0.2);
QIODevice m_audioIODevice = m_audioOutput->start();
5. 接收pcm 并加入输出设备
connect(m_audioPlay, &AudioPlay, this, &MainWindow::playAudio);
m_audioPlay->start();
void playAudio(QByteArray pcm){
if(pcm.isEmpty())
return;
QMutexLocker locker(&m_pcmMutex);
m_pcmQueue.append(pcm);
}
6. 定时器:每10ms从队列取数据写入设备
m_feedTimer = new QTimer(this);
connect(m_feedTimer, &QTimer::timeout, this, &MainWindow::feedAudio);
m_feedTimer->start(10);
void feedAudio(){
if(!m_audioIODevice || !m_audioOutput)
return;
QMutextLocker locker(&m_pcmMutext);
while(!m_pcmQueue.empty()){
// 获取音频设备缓冲区当前剩余可写入字节数
int free = m_audioOutput.bytesFree();
if(free < 0) break;
// 获取 剩余字节数 和 缓冲队列剩余字节数 中的最小值
int minBytes = qMin(free, m_pcmQueue.size());
int written = m_audioIODevice->write(m_pcmQueue.constData, minBytes);
if(written > 0)
// 将写入的字节从缓冲队列移除
m_pcmQueue.remove(0, minBytes);
else
break;
}
}