播放器开发(六):音频帧处理并用SDL播放

目录

学习课题:逐步构建开发播放器【QT5 + FFmpeg6 + SDL2】

步骤

AudioOutPut模块

1、初始化【分配缓存、读取信息】

2、开始线程工作【从队列读帧->重采样->SDL回调->写入音频播放数据->SDL进行播放】

主要代码

分配缓存

// 对于样本队列
av_audio_fifo_alloc(playSampleFmt, playChannels, spec.samples * 5);// 对于帧的音频字节数据
// 首次计算帧大小,并且开辟缓冲区    
maxOutSamples = (int) av_rescale_rnd(decCtxSamples, playSampleRate, srcSampleRate, AV_ROUND_UP);
audioBufferSize = av_samples_get_buffer_size(nullptr, srcChannels, maxOutSamples, playSampleFmt, 0);
audioBuffer = (uint8_t *) av_malloc(audioBufferSize);

重采样相关

//配置重采样器参数
swr_alloc_set_opts2(&swrContext,&srcChannelLayout, playSampleFmt, playSampleRate,&srcChannelLayout, AVSampleFormat(srcSampleFmt), srcSampleRate,0, nullptr);
//初始化重采样器
swr_init(swrContext);//重采样流程
// 计算重采样后要输出多少样本数delay = swr_get_delay(swrContext, sample_rate);out_samples = (int) av_rescale_rnd(nb_samples + delay,playSampleRate,sample_rate,AV_ROUND_DOWN);// 判断预测的输出样本数是否>本次任务的最大样本数if (out_samples > maxOutSamples) {// 释放缓冲区,重新初始化缓冲区大小av_freep(&audioBuffer);audioBufferSize = av_samples_get_buffer_size(nullptr, srcChannels, out_samples, playSampleFmt, 0);audioBuffer = (uint8_t *) av_malloc(audioBufferSize);maxOutSamples = out_samples;}playSamples = swr_convert(swrContext, &audioBuffer, out_samples, (const uint8_t **) frame->data, nb_samples);

SDL的音频回调

// SDL音频回调函数提供了一个回调接口,可以让我们在音频设备需要数据的时候向里面写入数据
// 从而进行声音播放
// 回调函数示例 函数名自定义,放在类中需要加静态(static)
void AudioOutPut::AudioCallBackFunc(void *userdata, Uint8 *stream, int len) {//userdata 是在初始化时赋值的,有时候会把类中"this"传进去//stream 是音频流,在回调函数中需要把音频数据写入到stream就可以实现声音播放//len是由SDL传入的SDL缓冲区的大小,如果这个缓冲未满,我们就一直往里填充数据...
}

完整模块

AudioOutPut

//AudioOutPut.h
#include "FFmpegHeader.h"
#include "SDL.h"
#include "queue/AVFrameQueue.h"
#include <QDebug>
#include <QObject>
#include <QtGui>
#include <QtWidgets>
#include <thread>class AudioOutPut {
private:std::thread *m_thread;bool isStopped = true; // 是否已经停止 停止时退出线程bool isPlaying = false;// 是否正在播放bool isPause = false;  // 是否暂停void run();int resampleFrame(AVFrame *frame);int sdlCallBackMode = 1;QString url;            //视频地址uint8_t *audioBuffer;   //存储解码后音频bufferint audioBufferSize = 0;//buffer大小int audioBufferIndex = 0;SDL_mutex *mtx = nullptr;// 队列锁SDL_AudioDeviceID audioDevice;AVAudioFifo *fifo = nullptr;//Audio BufferAVFrameQueue *frameQueue;    //解码后的帧队列SwrContext *swrContext;      //重采样上下文// 解码器上下文AVCodecContext *decCtx;          // 音频解码器上下文int srcChannels;                 // 源通道数AVChannelLayout srcChannelLayout;// 源通道布局enum AVSampleFormat srcSampleFmt;// 源采样格式int srcSampleRate;               // 源音频采样率// playerint maxOutSamples; // 最大样本数,用于计算缓存区大小int playSamples;   // 最终播放的样本数int playSampleRate;// 最终播放的音频采样率enum AVSampleFormat playSampleFmt;int playChannels;// 源通道数
public:AudioOutPut(AVCodecContext *dec_ctx, AVFrameQueue *frame_queue);int init(int mode = 1);static void AudioCallBackFunc(void *userdata, Uint8 *stream, int len);//SDL音频回调函数实体普通版void AudioCallBack(Uint8 *stream, int len);//SDL音频回调函数实体队列版void AudioCallBackFromQueue(Uint8 *stream, int len);int start();
};//AudioOutPut.cpp
#include "AudioOutPut.h"
AudioOutPut::AudioOutPut(AVCodecContext *dec_ctx, AVFrameQueue *frame_queue): decCtx(dec_ctx), frameQueue(frame_queue) {srcSampleFmt = decCtx->sample_fmt;srcSampleRate = decCtx->sample_rate;srcChannelLayout = decCtx->ch_layout;srcChannels = srcChannelLayout.nb_channels;
}
int AudioOutPut::init(int mode) {sdlCallBackMode = mode;// SDL initif (SDL_Init(SDL_INIT_AUDIO) != 0) {qDebug() << "SDL_INIT_AUDIO error";return -1;}SDL_AudioSpec wanted_spec, spec;wanted_spec.channels = decCtx->ch_layout.nb_channels;wanted_spec.freq = decCtx->sample_rate;SDL_AudioFormat sample_type;switch (srcSampleFmt) {case AV_SAMPLE_FMT_FLTP:case AV_SAMPLE_FMT_FLT:sample_type = AUDIO_F32SYS;break;case AV_SAMPLE_FMT_U8P:case AV_SAMPLE_FMT_U8:sample_type = AUDIO_U8;break;case AV_SAMPLE_FMT_S64P:case AV_SAMPLE_FMT_S64:case AV_SAMPLE_FMT_S32P:case AV_SAMPLE_FMT_S32:sample_type = AUDIO_S32SYS;break;case AV_SAMPLE_FMT_S16P:case AV_SAMPLE_FMT_S16:sample_type = AUDIO_S16SYS;break;default:sample_type = AUDIO_S16SYS;qDebug() << "不支持的采样格式:AVSampleFormat(" << srcSampleFmt << ")";}wanted_spec.format = sample_type;wanted_spec.silence = 0;wanted_spec.callback = AudioCallBackFunc;wanted_spec.userdata = this;wanted_spec.samples = decCtx->frame_size;int ret;//    ret = SDL_OpenAudio(&wanted_spec, &spec);audioDevice = SDL_OpenAudioDevice(NULL, 0, &wanted_spec, &spec, SDL_AUDIO_ALLOW_ANY_CHANGE);if (audioDevice == 0) {qDebug() << "SDL_OpenAudio error";return -1;}playChannels = spec.channels;playSampleRate = spec.freq;playSampleFmt = av_get_packed_sample_fmt(srcSampleFmt);if (mode == 1) {fifo = av_audio_fifo_alloc(playSampleFmt, playChannels, spec.samples * 5);}ret = swr_alloc_set_opts2(&swrContext,&srcChannelLayout, playSampleFmt, playSampleRate,&srcChannelLayout, AVSampleFormat(srcSampleFmt), srcSampleRate,0, nullptr);if (ret != 0) {qDebug() << "swr_alloc_set_opts2错误";return -1;}if (!swrContext) {qDebug() << "创建音频重采样上下文错误 swr_alloc";return -1;}ret = swr_init(swrContext);if (ret < 0) {qDebug() << "初始化音频重采样上下文错误 swr_init";return -1;}// 解码器上下文保存的帧样本数int decCtxSamples = 1024;if (decCtx->frame_size > 1024) {decCtxSamples = decCtx->frame_size;}// 首次计算帧大小,并且开辟缓冲区maxOutSamples = (int) av_rescale_rnd(decCtxSamples, playSampleRate, srcSampleRate, AV_ROUND_UP);audioBufferSize = av_samples_get_buffer_size(nullptr, srcChannels, maxOutSamples, playSampleFmt, 0);audioBuffer = (uint8_t *) av_malloc(audioBufferSize);return 1;
}void AudioOutPut::AudioCallBackFunc(void *userdata, Uint8 *stream, int len) {AudioOutPut *player = (AudioOutPut *) userdata;if (player->sdlCallBackMode == 1) {player->AudioCallBackFromQueue(stream, len);} else {player->AudioCallBack(stream, len);}
}void AudioOutPut::AudioCallBack(Uint8 *stream, int len) {int len1;// sdl的内部stream可用空间/*   len是由SDL传入的SDL缓冲区的大小,如果这个缓冲未满,我们就一直往里填充数据 */while (len > 0) {/*  audioBufferIndex 和 audioBufferSize 标示我们自己用来放置解码出来的数据的缓冲区,*//*   这些数据待copy到SDL缓冲区, 当audioBufferIndex >= audioBufferSize的时候意味着我*//*   们的缓冲为空,没有数据可供copy,这时候需要调用audio_decode_frame来解码出更多的桢数据 */if (audioBufferIndex >= audioBufferSize) {AVFrame *frame = frameQueue->pop(10);if (frame) {audioBufferSize = resampleFrame(frame);/* audioBufferSize < 0 标示没能解码出数据,我们默认播放静音 */if (audioBufferSize <= 0) {/* silence */audioBufferSize = 1024;/* 清零,静音 */memset(audioBuffer, 0, audioBufferSize);}}audioBufferIndex = 0;}/*  当audioBufferIndex < audioBufferSize 查看stream可用空间,决定一次copy多少数据,剩下的下次继续copy */len1 = audioBufferSize - audioBufferIndex;// 可用空间>if (len1 > len) {len1 = len;}if (audioBuffer == nullptr) return;memcpy(stream, (uint8_t *) audioBuffer + audioBufferIndex, len1);len -= len1;stream += len1;audioBufferIndex += len1;}
}void AudioOutPut::AudioCallBackFromQueue(Uint8 *stream, int len) {//由于AVAudioFifo非线程安全,且是子线程触发此回调,所以需要加锁SDL_LockMutex(mtx);//读取队列中的音频数据av_audio_fifo_read(fifo, (void **) &stream, playSamples);SDL_UnlockMutex(mtx);
}
int AudioOutPut::start() {SDL_PauseAudioDevice(audioDevice, 0);//    SDL_PauseAudio(0);if (sdlCallBackMode == 1) {m_thread = new std::thread(&AudioOutPut::run, this);if (!m_thread->joinable()) {qDebug() << "AudioOutPut音频帧处理线程创建失败";return -1;}}isStopped = false;isPlaying = true;return 0;
}
void AudioOutPut::run() {AVFrame *frame;while (!isStopped) {frame = frameQueue->pop(10);if (frame) {audioBufferSize = resampleFrame(frame);while (true) {SDL_LockMutex(mtx);if (av_audio_fifo_space(fifo) >= playSamples) {av_audio_fifo_write(fifo, (void **) &audioBuffer, playSamples);SDL_UnlockMutex(mtx);av_frame_unref(frame);break;}SDL_UnlockMutex(mtx);//队列可用空间不足则延时等待SDL_Delay((double) playSamples / playSampleRate);}}}
}
int AudioOutPut::resampleFrame(AVFrame *frame) {int64_t delay;  // 重采样后延迟int out_samples;// 预测的重采样后的输出样本数int sample_rate;// 帧原采样率int nb_samples; // 帧原样本数sample_rate = frame->sample_rate;nb_samples = frame->nb_samples;// 计算重采样后要输出多少样本数delay = swr_get_delay(swrContext, sample_rate);out_samples = (int) av_rescale_rnd(nb_samples + delay,playSampleRate,sample_rate,AV_ROUND_DOWN);// 判断预测的输出样本数是否>本次任务的最大样本数if (out_samples > maxOutSamples) {// 释放缓冲区,重新初始化缓冲区大小av_freep(&audioBuffer);audioBufferSize = av_samples_get_buffer_size(nullptr, srcChannels, out_samples, playSampleFmt, 0);audioBuffer = (uint8_t *) av_malloc(audioBufferSize);maxOutSamples = out_samples;}playSamples = swr_convert(swrContext, &audioBuffer, out_samples, (const uint8_t **) frame->data, nb_samples);if (playSamples <= 0) {return -1;}return av_samples_get_buffer_size(nullptr, srcChannels, playSamples, playSampleFmt, 1);
}

PlayerMain

添加音频输出代码


AudioOutPut *audioOutPut;
audioOutPut = new AudioOutPut(audioDecodeThread->dec_ctx, &audioFrameQueue);
audioOutPut->init(1);
audioOutPut->start();

测试运行结果

如果需要同时执行视频和音频的输出,记得要在解复用模块那把限制队列大小的位置把视频队列的大小限制给去掉。


目前只是实现了音频播放和视频渲染显示画面,但是可以看到音频和视频是不同步的,下一章我们就要让音频和视频同步起来。

播放器开发(六):音频帧处理并用SDL播放结果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/187888.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

实验五 C语言函数程序设计习题 (使用函数计算两点间的距离,请编写函数fun,使用函数输出字符矩阵,使用函数求最大公约数和最小公倍数)

1. 使用函数计算两点间的距离&#xff1a;给定平面任意两点坐标(x1,y1)和(x2,y2)&#xff0c;求这两点之间的距离(保留2位)小数。要求定义和调用dist(x1,y1,x2,y2)计算两点间的距离。坐标中两点坐标之间的距离公式如下&#xff1a; #include <stdio.h> #include <math…

1.ORB-SLAM3中如何保存多地图、关键帧、地图点到二进制文件中

1 保存多地图 1.1 为什么保存(视觉)地图 因为我们要去做导航&#xff0c;导航需要先验地图。因此需要保存地图供导航使用&#xff0c;下面来为大家讲解如何保存多地图。 1.2 保存多地图的主函数SaveAtlas 2051 mStrSaveAtlasToFile是配置文件中传递的参数&#xff1a; 这里我们…

ssh远程连接阿里云CentOS:修改为密码登录

文章目录 控制台添加密钥下载Xshell修改密码连接重启服务&#xff1a;重复Xshell使用密码登录 控制台添加密钥 会下载一个pem文件 下载Xshell 新建 通过public key登录 修改密码连接 passwd root然后输入你想要设置的密码两遍 cd /etc/ssh/ vi sshd_config将PasswordAuth…

[论文精读]利用大语言模型对扩散模型进行自我修正

本博客是一篇最新论文的精读&#xff0c;论文为UC伯克利大学相关研究者新近(2023.11.27)在arxiv上上传的《Self-correcting LLM-controlled Diffusion Models》 。 内容提要: 现有的基于扩散的文本到图像生成模型在生成与复杂提示精确对齐的图像时仍然存在困难,尤其是需要数值和…

研习代码 day45 | 动态规划——子序列问题

一、最长递增子序列 1.1 题目 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而不改变其余元素的顺序。例如&#xff0c;[3,6,2,7] 是数组 [0,3,1,6,2,2,…

python代码打包成.so的方法

前提条件&#xff1a; 确保电脑已经安装gcc且终端能检索到确保Python中已经安装cython包&#xff0c;若未安装&#xff0c;则先使用pip install cython进行安装 打包方法&#xff1a; step1&#xff1a;编写编译脚本setup.py&#xff0c;代码如下&#xff1a; # encoding ut…

生成对抗网络(DCGAN)手写数字生成

文章目录 一、前言二、前期工作1. 设置GPU&#xff08;如果使用的是CPU可以忽略这步&#xff09; 二、什么是生成对抗网络1. 简单介绍2. 应用领域 三、创建模型1. 生成器2. 判别器 四、定义损失函数和优化器1. 判别器损失2. 生成器损失 五、定义训练循环六、训练模型七、创建 G…

“前端八股文背诵版“,终于整理完了,堪称最强!

随着互联网的快速发展&#xff0c;前端开发领域成为了IT行业中的热门领域之一。很多求职者都希望能够进入这个领域&#xff0c;但是面对着如此激烈的竞争&#xff0c;很多人都感到无从下手。为了帮助大家更好地掌握前端开发的相关知识&#xff0c;小编整理了一份前端面试题合集…

单片机怎么实现真正的多线程?

单片机怎么实现真正的多线程? 不考虑多核情况时&#xff0c;CPU在一个时间点只能做一件事&#xff0c;因为切换的速度快所以看起来好像是同时执行多个线程而已。 实际上就是用定时器来做时基&#xff0c;以时间片的方式分别执行来实现的&#xff0c;只不过实现起来细节比较复…

网络安全应急响应-Server2228(环境+解析)

网络安全应急响应 任务环境说明: 服务器场景:Server2228(开放链接)用户名:root,密码:p@ssw0rd123

【代码】考虑差异性充电模式的电动汽车充放电优化调度matlab-yalmip-cplex/gurobi

程序名称&#xff1a;考虑差异性充电模式的电动汽车充放电优化调度 实现平台&#xff1a;matlab-yalmip-cplex/gurobi 代码简介&#xff1a;提出了一种微电网中电动汽车的协调充电调度方法&#xff0c;以将负荷需求从高峰期转移到低谷期。在所提出的方法中&#xff0c;基于充…

RHEL8.9 静默安装Oracle19C

RHEL8.9 静默安装Oracle19C 甘肃圆角网络科技开发有限公司 说明(GUI)&#xff1a;  1.实际业务场景中&#xff0c;Linux环境一般情况下是没有GUI的。没有GUI并不意味着没有安装图形界面。可能在部署Linux操作系统环境的时候安装了桌面环境&#xff0c;只是启动的时候设置了启动…

英国人工智能初创公司Stability AI面临卖身压力;深度学习中的检索增强生成简介

&#x1f989; AI新闻 &#x1f680; 英国人工智能初创公司Stability AI面临卖身压力 摘要&#xff1a;多位知情人士透露&#xff0c;英国人工智能初创公司Stability AI正寻求出售公司&#xff0c;因为投资者对其财务状况的压力越来越大。管理层最近几周一直将自己标榜为收购…

MapInfo Pro错误提示:This operation requires elevated privileges……

尝试删除MapInfo Pro时出现错误“此操作需要提升权限。将产品DVD/CD插入媒体播放器并双击setup.exe文件重新启动。此安装程序现在将中止。”。 原因&#xff1a; 这可能是由于权限问题。 解决方式&#xff1a; 1.如果MapInfo Pro setup.exe可用&#xff0c;请执行以下步骤&…

笔记64:Bahdanau 注意力

本地笔记地址&#xff1a;D:\work_file\&#xff08;4&#xff09;DeepLearning_Learning\03_个人笔记\3.循环神经网络\第10章&#xff1a;动手学深度学习~注意力机制 a a a a a a a a a a a

FIORI /N/UI2/FLP 始终在IE浏览器中打开 无法在缺省浏览器中打开

在使用/N/UI2/FLP 打开fiori 启动面板的时候&#xff0c;总是会在IE浏览器中打开&#xff0c;无法在缺省浏览器打开 并且URL中包含myssocntl 无法正常打开 启动面板 这种情况可以取消激活ICF节点/sap/public/myssocntl

java中@Async注解在CompletableFuture.runAsync里面使用没有生效的原因?

在Java中&#xff0c;Async注解通常与Spring框架一起使用以实现异步方法调用。然而&#xff0c;CompletableFuture.runAsync()是Java标准库中的方法&#xff0c;并不受Async注解的影响。 Async注解只能被Spring容器识别和处理&#xff0c;因此只能在由Spring管理的组件&#x…

spring boot 3.2.0 idea从零开始

spring boot 3.2.0 idea从零开始 最新的spring initilizer 不再支持低版本java&#xff0c;只能选择17、21 。 我也被迫尝试下最新版本的java。 jdk下载地址 自定义好artifact和group之后点击下一步。 在这里选择需要的组件&#xff0c;我准备做web项目所以只选择spring web …

word模板导出word文件

前期准备工作word模板 右键字段如果无编辑域 ctrlF9 一下&#xff0c;然后再右键 wps 直接 ctrlF9 会变成编辑域 pom.xml所需依赖 <dependencies> <!--word 依赖--> <dependency><groupId>fr.opensagres.xdocreport</groupId><artifactId…

vim工具以及如何给用户加上sudo的权限

Linux开发工具之vim以及如何给用户配置sudo的权限文件的操作 1.vim概念的介绍 2.vim的多模式的介绍 3.vim的命令模式与低行模式的相关指令操作 4.vim如何配置 5.如何给普通用户配置sudo的权限 本文开始~~~~ 1. vim概念的介绍 vim是一款多模式的文本编辑器&#xff0c;简单…