ffmpeg 音频播放器相关
时间:2022-06-10
本文章向大家介绍ffmpeg 音频播放器相关,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
每秒理论PCM大小
每秒理论PCM大小 = 采样率 * 声道数 * 位数/8
比如:
//44100hz 立体声 16bit
int s_time = 44100 * 2 * 16/8;
获取总时长
duration = pFormatCtx->duration / AV_TIME_BASE;
获取当前AVframe
时间
AVRational time_base = pFormatCtx->streams[audio_index]->time_base
double now_time = frame->pts * av_q2d(time_base);
获取当前播放时间
因为每一个AVframe
的pts
不一定都有,所以就需要自己手维护一个当前时间的变量
公式:PCM实际数据大小 / 每秒理论PCM大小;
clock += buffersize / ((double)(sample_rate * 2 * 2));
伪代码如下:
AVFormatContext *pFormatCtx = NULL;
//采样率
int sample_rate =0;
//当前总时长
int duration = 0;
AVRational time_base =NULL;
//当前AvFrame时间
int now_time=0;
//当前播放时长
int clock = 0;
//上次播放时长标识
int last_time=0;
uint8_t *buffer = NULL;
int data_size=0;
//获取到 pFormatCtx
...
for(int i = 0; i < pFormatCtx->nb_streams; i++)
{
if(pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)//得到音频流
{
...
sample_rate = pFormatCtx->streams[i]->codecpar->sample_rate;
duration= pFormatCtx->duration / AV_TIME_BASE;
time_base = pFormatCtx->streams[i]->time_base;
}
}
//解码数据
...
int nb = swr_convert(
swr_ctx,
&buffer,
avFrame->nb_samples,
(const uint8_t **) avFrame->data,
avFrame->nb_samples);
int out_channels = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);
data_size = nb * out_channels * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);
now_time = avFrame->pts * av_q2d(time_base);
if(now_time < clock){
now_time = clock;
}
clock = now_time;
//解码数据之后数据封装在 buffer,播放的时候
...
if(data_size>0){
//size/(采样率(44100hz) * 立体声*16bit/8)
clock += data_size / ((double)(sample_rate * 2 * 2));
//设置一个回掉最小相差值
if(clock - last_tiem >= 0.1) {
last_tiem = clock;
//回调应用层
callJava->onCallTimeInfo(CHILD_THREAD,clock, duration);
}
}
解码播放流程思路
采用多线程,生产者消费者模型,AVPacket入队,然后AVPacket出队解码播放,播放采用OpenSLES
release内存回收
当我们release
的时候,我们需要注意
- 为了确保线程完全退出,我们最好是
sleep
个几十毫秒,然后在释放相关内存,但是最好的是使用pthred_join
来同步线程退出。 - 有可能初始化未准备完毕我们就调用
release
,这时候最好是在初始化准备和release
加个线程锁。 - 初始化的时候有可能
avformat_open_input
打开网络链接,网络很卡,所以我们需要为pFormatCtx
加入一个interrupt_callback
来及时响应int avformat_callback(void *ctx) { WlFFmpeg *fFmpeg = (WlFFmpeg *) ctx; if(fFmpeg->playstatus->exit) { return AVERROR_EOF; } return 0; } pFormatCtx->interrupt_callback.callback = avformat_callback; pFormatCtx->interrupt_callback.opaque = this;
暂停,继续,停止播放,播放完成
暂停播放,继续播放采用OpenSLES的相关api,播放完成则在播放完毕的时候回掉即可
seek功能
在seek
的时候设置标志位并加锁,清空队列,标志位判断是否继续av_read_frame
,seek
完毕释放锁,还原标识位。即可重新读取最新数据
seek = true;
queue->clearAvpacket();
pthread_mutex_lock(&seek_mutex);
int64_t rel = secs * AV_TIME_BASE;
//重置内部解码器状态/刷新内部缓冲区
avcodec_flush_buffers(avCodecContext);
//主要是这个函数
avformat_seek_file(pFormatCtx, -1, INT64_MIN, rel, INT64_MAX, 0);
pthread_mutex_unlock(&seek_mutex);
seek = false;
pthread_mutex_lock(&seek_mutex);
ret = av_read_frame(pFormatCtx, packet);
pthread_mutex_unlock(&seek_mutex);
音量,声道切换
采用OpenSLES
的相关api
播放变速变调
OpenSL ES
可以实现变速播放,但是再改变速度的同时也改变了音调,这
种体验是不好的。所以采用SoundTouch
来实现,在播放的时候,对原始数据重新进行计算即可
计算pcm分贝大小
//char*是为了都转换成字节来处理
int WlAudio::getPCMDB(char *pcmcata, size_t pcmsize) {
int db = 0;
short int pervalue = 0;
double sum = 0;
for (int i = 0; i < pcmsize; i += 2) {
memcpy(&pervalue, pcmcata + i, 2);
sum += abs(pervalue);
}
sum = sum / (pcmsize / 2);
if (sum > 0) {
db = (int) 20.0 * log10(sum);
}
return db;
}
性能优化
- 由于解码用到了
while
循环,而不加睡眠的while
循环会使CPU
使用率提高30%
左右, 因此我们需要为解码线程加上一定的睡眠时间来降低CPU
使用率。 - 停止时回收创建的内存空间。
一个AVPacket对应多个AVFrame 比如.ape
格式的
这种情况就需要在解码的时候,设置一个标识来判断不停的解析AVPacket,avcodec_send_packet(avCodecContext, avPacket)
之后不停的avcodec_receive_frame
,知道读取完毕在设置标识。
一个AVPacket对应多个AVFrame引发的seek问题
由于一个AVPacket
里面有多个AVFrame
,当seek
时,FFmpeg
解码器中还残留AVFrame
,所以会导致seek
后,不能立即播放当前音乐。
解决方案就是seek的时候调用
avcodec_flush_buffers(avCodecContext)
- oracle工具集初探(r4笔记第8天)
- Spring+SpringMVC+MyBatis+easyUI整合优化篇(二)Log4j讲解与整合
- 京东JData算法大赛-高潜用户购买意向预测(github源码)
- 巧用linux命令做图片下载器(r4笔记第7天)
- Spring+SpringMVC+MyBatis+easyUI整合优化篇(四)单元测试实例
- Spring+SpringMVC+MyBatis+easyUI整合优化篇(五)结合MockMvc进行服务端的单元测试
- 关于order by中的数据排序(r4笔记第6天)
- 深度学习CTPN+CRNN模型实现图片内文字的定位与识别(OCR)
- Markdown语法讲解及MWeb使用教程
- 通过Linu命令实现屏幕录制和回放(r4笔记第5天)
- 用keras对国产剧评论文本的情感进行预测
- python常用可视化技巧
- Spring+SpringMVC+MyBatis+easyUI整合基础篇(六)maven整合SSM
- 通过java程序抽取日志中的sql语句(r4笔记第4天)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- TensorFlow-手写数字识别(三)
- 常用字节转换(字符串转16进制,16进制转字符串)
- TensorFlow-手写数字识别(二)
- TensorFlow-手写数字识别(一)
- OpenCV-简易答题卡识别
- Flink 原理详解
- FreeRTOS例程2-任务挂起恢复与使用中断遇到的坑!
- Puppeteer自动化的性能优化与执行速度提升
- PyTorch1: 张量的性质
- 分治算法
- 基于 Nginx + PHP-FPM 作为 HTTP 服务器
- 玩转 PhpStorm 系列(五):代码模板篇
- 通过 PDO 扩展与 MySQL 数据库交互(上)
- 通过 PDO 扩展与 MySQL 数据库交互(下)
- 使用 Vue 3.0,你可能不再需要Vuex了