Android audiotrack尾帧无声

前言

产品一直有用户反馈音频截断问题。在机遇巧合下现学现卖音频知识处理相关问题。

问题描述

我们查看以下简化播放器代码:


class AACPlayer(private val filePath: String) {private val TAG = "AACPlayer"private var extractor: MediaExtractor? = nullprivate var codec: MediaCodec? = nullprivate var audioTrack: AudioTrack? = nullfun play() {try {extractor = MediaExtractor().apply {setDataSource(filePath)}var trackIndex = -1for (i in 0 until extractor!!.trackCount) {val format = extractor!!.getTrackFormat(i)val mime = format.getString(MediaFormat.KEY_MIME)if (mime!!.startsWith("audio/")) {trackIndex = ibreak}}if (trackIndex >= 0) {extractor!!.selectTrack(trackIndex)val format = extractor!!.getTrackFormat(trackIndex)val mime = format.getString(MediaFormat.KEY_MIME)codec = MediaCodec.createDecoderByType(mime!!)codec!!.configure(format, null, null, 0)codec!!.start()val sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE)val channelConfig =if (format.getInteger(MediaFormat.KEY_CHANNEL_COUNT) == 1) AudioFormat.CHANNEL_OUT_MONO else AudioFormat.CHANNEL_OUT_STEREOval bufferSize = AudioTrack.getMinBufferSize(sampleRate,channelConfig,AudioFormat.ENCODING_PCM_16BIT);val audioTrackAttributes =AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build()val audioFormat = AudioFormat.Builder().setEncoding(AudioFormat.ENCODING_PCM_16BIT).setSampleRate(sampleRate).setChannelMask(channelConfig).build()audioTrack = AudioTrack.Builder().setAudioAttributes(audioTrackAttributes).setAudioFormat(audioFormat).setTransferMode(AudioTrack.MODE_STREAM).setBufferSizeInBytes(bufferSize).build()if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {logD("bufferSizeInFrames = [${audioTrack?.bufferSizeInFrames}] bufferCapacityInFrames = [${audioTrack?.bufferCapacityInFrames}] bufferSize = [${bufferSize}]  startThresholdInFrames = [${audioTrack!!.startThresholdInFrames}]")}audioTrack!!.play()val inputBuffers = codec!!.inputBuffersval outputBuffers = codec!!.outputBuffersval bufferInfo = MediaCodec.BufferInfo()var isEOS = falsewhile (!isEOS) {val inIndex = codec!!.dequeueInputBuffer(10000)if (inIndex >= 0) {val buffer = inputBuffers[inIndex]val sampleSize = extractor!!.readSampleData(buffer, 0)if (sampleSize < 0) {codec!!.queueInputBuffer(inIndex,0,0,0,MediaCodec.BUFFER_FLAG_END_OF_STREAM)isEOS = true} else {val presentationTimeUs = extractor!!.sampleTimecodec!!.queueInputBuffer(inIndex, 0, sampleSize, presentationTimeUs, 0)extractor!!.advance()}}var outIndex = codec!!.dequeueOutputBuffer(bufferInfo, 10000)while (outIndex >= 0) {val outBuffer = outputBuffers[outIndex]val bufferBackup = outBuffer.slice()if (outBuffer.remaining() <= 0) {continue}//仅仅为了打印无他用val array = ByteArray(bufferBackup.remaining())bufferBackup.get(array, 0, array.size)logD(array.joinToString(transform = { String.format("%02x", it) }))logD("写入数据大小${array.size} hashCode ${array.contentHashCode()}")audioTrack!!.write(outBuffer,outBuffer.remaining(),AudioTrack.WRITE_BLOCKING)codec!!.releaseOutputBuffer(outIndex, false)outIndex = codec!!.dequeueOutputBuffer(bufferInfo, 0)}}}} catch (e: Exception) {e.printStackTrace()} finally {extractor?.release()codec?.stop()codec?.release()audioTrack?.flush()audioTrack?.stop()audioTrack?.release()}}fun logD(msg:String) {Log.d(TAG, msg)}
}

这也是网上充斥最多的示例代码,但是上面的代码丢失尾帧的音频的问题。

getStartThresholdInFrames文档

Androidaudiotrack有一个缓冲区,调用则可以阻塞或阻塞式使用audiotrack.write向里面写入数据。播放器为提高效率在缓冲大于startThresholdInFrames时取出进行播放。startThresholdInFrames一般大于等于bufferSizeInFrames

你播放音频时不敢保证所有音频数据都是对齐startThresholdInFrames,所以你会以为调用audiotrack.flush可以解决问题了。但是我们阅读相关文档flush文档发现这个API只是丢弃之前的数据,加速audiotrack.write

在这里插入图片描述
解决方案 在音频流写入结束调用audiotrack.stop 这个函数会将未播放的数据进行加载播放在结束。audiotrack.stop文档
在这里插入图片描述
在上述的代码我们如下编写:

class AACPlayer(...) {//...fun play() {try {while (outIndex >= 0) {//....略//结束调用stop刷出残余音频if (bufferInfo.size == 0&& (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {audioTrack!!.stop()break}//....略}}catch(...){//...}finally{} finally {extractor?.release()codec?.stop()codec?.release()audioTrack?.flush()//注释多余的stop//audioTrack?.stop()audioTrack?.release()}}
}

当然你如果比较骚可以进行补帧操作

class AACPlayer(...) {//...fun play() {try {while (outIndex >= 0) {//....略if (bufferInfo.size == 0&& (bufferInfo.flags and MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {var interpolationFrame =(audioTrack!!.startThresholdInFrames / bufferSize) + 1while (interpolationFrame > 0) {interpolationFrame--;audioTrack!!.write(ByteArray(bufferSize), 0, bufferSize)}break}//....略}}catch(...){//...}finally{} finally {//略}}
}

实践

我们有一个极短音频且有效音在末尾,那么在部分手机上将无法听到这个音频
在这里插入图片描述
在某手机上相关输出参数如下:

bufferSizeInFrames = [11310] 
bufferCapacityInFrames = [11310] 
bufferSize = [45240]  
startThresholdInFrames = [11310]

这个文件对应的PCM数据40960(A000h)字节大小。
我们看下这个文件的末端可以看到很多有效数据。
在这里插入图片描述
我们在看看文件最前面PCM数据 全是空数据。
在这里插入图片描述
所以这个文件只有末尾才音频。
我们算一下Audiotrack刷新次数

文件PCM大小/Audiotrack刷新阈值 =40960/11310 = 3.6

假设如果我们有效音频在最后0.6将无法播放

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

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

相关文章

TortoiseSVN 报错:The server unexpectedly closed the connetion

前言 CentOS7Linux 安装subversionmod_dav_svn&#xff0c;搭建subversion(svn)服务器 The server unexpectedly closed the connetion 解决办法 重启Apache服务 shell> systemctl restart httpd

uniapp—day02

个人名片&#xff1a; &#x1f60a;作者简介&#xff1a;一名大二在校生 &#x1f921; 个人主页&#xff1a;坠入暮云间x &#x1f43c;座右铭&#xff1a;给自己一个梦想&#xff0c;给世界一个惊喜。 &#x1f385;**学习目标: 坚持每一次的学习打卡 文章目录 WXML 和HTML区…

系统及其分类

系统定义 系统&#xff1a;指若干相互关联的事物组合而成的具有特定功能的整体。 系统的基本作用&#xff1a;对输入信号进行加工和处理&#xff0c;将其转换为所需要的输出信号。 系统分类 系统的分类错综复杂&#xff0c;主要考虑其数学模型的差异来划分不同类型。主要分为…

怎么绕过CDN查找真实IP

一、怎么绕过CDN查找真实IP 首先使用全球ping查看该域名是否存在cdn 方法一&#xff1a;使用phpinfo等探针的方式找到真实IP 方法二&#xff1a;网站根域或子域查找真实IP 大部分CDN服务都是按流量进行收费的&#xff0c;所以一些网站管理员只给重要业务部署CDN&#xff0c…

大华智慧园区综合管理平台 clientServer SQL注入漏洞复现

0x01 产品简介 “大华智慧园区综合管理平台”是一款综合管理平台,具备园区运营、资源调配和智能服务等功能。平台意在协助优化园区资源分配,满足多元化的管理需求,同时通过提供智能服务,增强使用体验。 0x02 漏洞概述 由于大华智慧园区综合管理平台clientServer接口处未…

使用reprepro+nginx搭建apt服务器

目录 项目背景 项目要求 项目开发过程 1、apt服务器的搭建 2、实现自定义指定源文件列表来实现apt update更新 3、实现软件启动时自动更新 4. source.list中镜像源地址的格式 项目开发的难点/坑点 总结 项目背景 前面写过一篇“利用Nginx搭建一个apt服务器”&#xff…

STM32嵌入式开发需要掌握硬件、嵌入式系统、C编程语言以及相关的外设驱动等知识

学习STM32嵌入式开发需要掌握硬件、嵌入式系统、C编程语言以及相关的外设驱动等知识。以下是学习STM32的路线及重要的学习内容&#xff1a; 阶段学习内容目标1.基础知识- 理解嵌入式系统的基本概念和原理 - 了解STM32系列微控制器的特点和应用领域掌握嵌入式系统基本概念&…

Midjourney绘图欣赏系列(十)

Midjourney介绍 Midjourney 是生成式人工智能的一个很好的例子&#xff0c;它根据文本提示创建图像。它与 Dall-E 和 Stable Diffusion 一起成为最流行的 AI 艺术创作工具之一。与竞争对手不同&#xff0c;Midjourney 是自筹资金且闭源的&#xff0c;因此确切了解其幕后内容尚不…

用Docker Compose实现负载均衡【入门篇】

在本文中&#xff0c;我们将讨论如何使用Docker Compose管理多个容器&#xff0c;并实现负载均衡。 首先&#xff0c;让我们简要介绍一下Docker Compose。Docker Compose是一个用于定义和运行多个Docker容器的工具&#xff0c;通过一个单独的文件来描述应用程序的整个服务架构…

vue项目:webpack打包优化实践

本文目录 一、项目基本信息二、分析当前项目情况1、使用 webpack-bundle-analyzer 插件2、使用 speed-measure-webpack-plugin 插件 三、解决构建问题1、caniuse-lite 提示的问题2、 warning 问题 四、打包速度优化1、修改source map2、处理 loader 五、webpack性能优化1、使用…

idea+maven+tomcat+spring 创建一个jsp项目

概述&#xff1a;我真服了&#xff0c;这个垃圾学校还在教jsp&#xff0c;这种技术我虽然早会了&#xff0c;但是之前搞的大多都是springboot web类型的&#xff0c;这里我就复习一下&#xff0c;避免以后忘记这种垃圾技术 第一步&#xff1a;创建maven项目 第二步&#xff1a…

adb shell 指令集

1.connect device连接设备&#xff1a; adb devices #return: List of devices attached 0123456789ABCDEF device2.连接终端 adb shell从设备拷贝文件到本地 adb pull <remote> [local] 如: adb pull /sdcard/demo.txt e:\从到本地拷贝文件到设备 adb push &…

HUAWEI 华为交换机 配置 MAC 地址漂移检测示例

组网需求 如 图 2-17 所示&#xff0c;网络中两台 LSW 间网线误接形成了网络环路&#xff0c;引起 MAC 地址发生漂 移、MAC 地址表震荡。 为了能够及时检测网络中出现的环路&#xff0c;可以在 Switch 上配置 MAC 地址漂移检测功能&#xff0c; 通过检测是否发生MAC 地址漂移…

从零搭建Vue项目

目录 环境准备 NodeJS安装 ​编辑 2. 选择安装目录 3. 验证NodeJS环境变量 4. 配置npm的全局安装路径 5. 切换npm的淘宝镜像 6. 安装Vue-cli Vue项目创建 1. 打开UI界面 2. 打开项目管理器 3. 创建项目 vue项目目录结构介绍 运行vue项目 Vue项目开发流程 Vue组…

JAVA的多线程及并发

1. Java 中实现多线程有几种方法 继承 Thread 类&#xff1b; 实现 Runnable 接口&#xff1b; 实现 Callable 接口通过 FutureTask 包装器来创建 Thread 线程&#xff1b; 使 用 ExecutorService 、 Callable 、 Future 实 现 有 返 回 结 果 的多 线 程 &#xff08; 也 就 …

十五、计算机视觉-sobel算子

文章目录 前言一、sobel算子的概念二、sobel算子的计算方式三、具体实现 前言 上节课我们学习了梯度的知识&#xff0c;学习了如何去计算梯度&#xff0c;本节我们继续学习计算梯度的方法&#xff0c;本节我们学习使用Sobel算子计算梯度&#xff0c;这与上节课梯度计算方法有所…

jmeter发送请求参数如何使用变量

问题描述 发送jmeter请求时&#xff0c;想设置请求参数为变量 解决方法

190基于matlab的tfrSTFT时频分布图

基于matlab的tfrSTFT时频分布图&#xff0c;计算时间序列的STFT时频分布图&#xff0c;得到瞬时频率。通过GUI可以调节图像的展示样式。程序已调通&#xff0c;可直接运行。 190 STFT时频分布图 瞬时频率 能量谱 (xiaohongshu.com)

GPT R 生态环境领域数据统计分析

自2022年GPT&#xff08;Generative Pre-trained Transformer&#xff09;大语言模型的发布以来&#xff0c;它以其卓越的自然语言处理能力和广泛的应用潜力&#xff0c;在学术界和工业界掀起了一场革命。在短短一年多的时间里&#xff0c;GPT已经在多个领域展现出其独特的价值…

Django调用mysql

Django——数据库 1、ORM ORM 是 Django提供的内置框架 &#xff0c; 是和一些关系型数据库的连接接口&#xff0c;使用类对象的方式操作数据(增删改查)。 ORM 将类对象的操作语句转换为 sql 语句&#xff0c;将在数据库中查询得到的结果转换为对象的格式进行返回 在 Djang…