当前位置: 首页 > news >正文

FFmpeg + qt 音频播放

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;

}

}

http://www.jsqmd.com/news/1384738/

相关文章:

  • Vue3低代码平台物料模式配置:从Schema设计到AI智能生成
  • WAIC2026 国产超节点算力浪潮下@ACP#IX9104 在算力矩阵中的定位与落地场景
  • Windows注册表REG_QWORD与REG_BINARY数据类型读取与解析实战指南
  • Windows Defender完全移除实战指南:专业级系统优化工具深度解析
  • Java 随机生成6位数字
  • C语言连续输入问题:从缓冲区原理到实战解决方案
  • 02NylonME AI记忆引擎:你的 AI Agent 为什么总是交付不了?因为架构从一开始就错了
  • 寒地专网通信工程落地思考|扎根龙江,黑龙江移远科技本地化无线通信解决方案实践
  • AutoDock Vina分子对接教程:零基础完成第一次对接的6个关键步骤
  • OpenCore Legacy Patcher深度技术探索:内存注入机制与老Mac升级方案原理揭秘
  • 网盘直链解析工具终极指南:告别限速,释放下载潜能
  • 2026年工厂智能照明改造公司实力榜:上海凡特与五大主流品牌怎么选 - 品牌报告
  • 阿里云Elasticsearch日志采集与加工服务:一体化托管方案解析与实践
  • Shell脚本入门到实战:自动化运维与文本处理核心技巧
  • Qwen多模态工具层:本地AI智能体开发与部署实战指南
  • 2026年河北电梯无线五方对讲挑选攻略 鑫洋电子等企业梳理 - 小范同学a
  • 人力资源公司残保金怎么算?公式详解 - 天下观知
  • CentOS 7部署MinIO对象存储:从系统配置到服务优化的完整指南
  • 腾讯 WorkBuddy 实操:AI 数字员工工作台 + 一人公司 4.0(FDE)完整指南
  • 动态规划核心思想与实战:从斐波那契到背包问题
  • 抖音下载工具 douyin-downloader 上手指南:去水印、批量下载、增量备份一次讲清
  • SPT-AKI存档编辑器完全指南:5分钟掌握角色、商人、任务与技能的终极修改方案
  • 国风与赛博朋克风对比:使用知漫剧分析不同风格下AI漫剧怎么制作的跑图参数
  • Muse Glimmer 推测性解码实战:加速大模型推理的本地部署指南
  • 一招重置Windows更新组件:Reset Windows Update Tool让我摆脱0x80070002报错的纠缠
  • MySQL无符号整数深度解析:从二进制原理到实战选型指南
  • AI+3D人工智能全链路实战培训班2026年火热招生中 - 武汉学历升学规划
  • 从词向量到AI语义理解:揭秘“国王-男人+女人=女王”背后的向量运算原理与实践
  • 网站建设公司哪家好?2026年十大网站设计服务商深度评测 - 天下观知
  • Windows Cleaner终极指南:5步彻底解决C盘爆红问题的免费开源神器