Android MediaPlayer多次Seek产生杂音优化

news2024/9/21 0:51:36

前言

MediaPlayer 作为Android自带的Player目前还是存在很多不好使用问题,但实际开发中,还是有不少使用场景,本文针对多次seek产生杂音的问题进行分析讨论,自己遇到了进行记录,目前底层也不好解决和轻易改动原生代码,只能通过应用层兼容

1.为什么会产生杂音

多次seek音视频
从图中可以看出,是因为产生的两次start,那我们就得研究这两次start如何产生的

2.单次Seek场景

pause - seek - resume(恢复pause前记录的状态)
resume即代表了你之前播放就是继续播放,之前暂停则会继续暂停

3.多次seek的场景

setOnCompletionListener

在这里插入图片描述
这是我实现视频播放器多次seek的自定义日志
日志TAG解释

**pauseByEvent 主动暂停**
startTouch 开始按住(手势滑动 或者seekbar)
seek   拖动进度,或者手按住的时候抖动
stopTouch 松开 (手势滑动 或者seekbar)
**resumeByEvetn 主动**
seekComplete 监听seek结束
  • 这里得解释下为什么会有pauseByEvent 主动暂停和resumeByEvetn 主动恢复,根据【2.单次Seek场景】,单次seek是自动会记录,暂停和恢复的,但是实际场景中,你在拖动过程中是不能有声音的,它单次seek完就出声了,显然是不满足需求的

问题最后一次就出现了回调了两个seekComplete,根据【2.单次Seek场景】,自然就出现了多次带间隔的pause和start杂音就产生了

4.溯本求源

/aosp13/frameworks/av/media/libmedia/mediaplayer.cpp 

  // cache duration
577          mCurrentPosition = msec;
578          mCurrentSeekMode = mode;
579          if (mSeekPosition < 0) {
580              mSeekPosition = msec;
581              mSeekMode = mode;
582          return mPlayer->seekTo(msec, mode);
---------------------------------------------------------
930      case MEDIA_SEEK_COMPLETE:
931          ALOGV("Received seek complete");
932          if (mSeekPosition != mCurrentPosition || (mSeekMode != mCurrentSeekMode)) {
933              ALOGV("Executing queued seekTo(%d, %d)", mCurrentPosition, mCurrentSeekMode);
934              mSeekPosition = -1;
935              mSeekMode = MediaPlayerSeekMode::SEEK_PREVIOUS_SYNC;
936              seekTo_l(mCurrentPosition, mCurrentSeekMode);
937          }
938          else {
939              ALOGV("All seeks complete - return to regularly scheduled program");
940              mCurrentPosition = mSeekPosition = -1;
941              mCurrentSeekMode = mSeekMode = MediaPlayerSeekMode::SEEK_PREVIOUS_SYNC;
942          }
943          break;

查看源码,我们可以看到无论多少次seek,cache最多有两个seek,mSeekPosition 小于0则不会执行seek,complete不相等则执行新的mSeekPosition 则不会为-1

5.记录seek缓存及其执行次数

根据log实际分析再结合源码,我们得到以下简单的工具类

/**
 * @author rex
 * @date 2023/6/9 17:13
 * 解决多次seek杂音的问题
 * seek 在mediaplayer中最多缓存两次seek(MAX_CACHE_SEEK),再松开seek后计数为0则适合resume-play
 */
object HandlerSeekNum {

    const val TAG = "HandlerSeekNum"
    const val MAX_CACHE_SEEK = 2
    const val FINISH = 0
    var isOnStartTrackingTouch = false

    var seekNum = FINISH

    fun reset() {
        seekNum = FINISH
        SLog.d(TAG, "reset 0")
    }

    fun add() {
        seekNum++
        if (seekNum > MAX_CACHE_SEEK) {
            seekNum = MAX_CACHE_SEEK
        }
        SLog.d(TAG, "add $seekNum")

    }

    fun reduce() {
        seekNum--
        if (seekNum < FINISH) {
            seekNum = FINISH
        }
        SLog.d(TAG, "reduce $seekNum")

    }

    fun isFinish(): Boolean {
        return seekNum == FINISH
    }

}

6.将工具类用于seek事件

[seekBar.setOnSeekBarChangeListener]


           override fun onStartTrackingTouch(seekBar: SeekBar?) {
                SLog.e(TAG, "onStartTrackingTouch")
                isOnStartTrackingTouch = true
                HandlerSeekNum.reset()
                mOnOverlayCallback?.pauseByEvent()


            }

            override fun onStopTrackingTouch(seekBar: SeekBar?) {

                isOnStartTrackingTouch = false    
                SLog.e(TAG, "onStopTrackingTouch")
				mOnOverlayCallback?.seek(progress)
				// 此处为seek完成的较快抬手回调马上结束了 此时也可以直接播放
                if (HandlerSeekNum.isFinish()) {
                    mOnOverlayCallback?.resumeByEvent()
            }
	}
			override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {

   				HandlerSeekNum.add()
                SLog.i(TAG, "seek position $position")
                PlayerManager.getInstance().seekTo(position)
                
            }

7.监听seekComplete

[mediaPlayer.setOnSeekCompleteListener ]
1.判断seek彻底结束
2.手已经松开
3.恢复播放

  setOnSeekCompleteListener {
                    SLog.d(TAG, "seekComplete:${player?.currentPosition}")
                    HandlerSeekNum.reduce()
                    if (HandlerSeekNum.isFinish() && !HandlerSeekNum.isOnStartTrackingTouch) {
                        PlayerManager.getInstance().resumeByEvent()
                    }
                }

8.结果LOG验证,此时再验证波形已没有seek产生的杂音

# 7.监听seekComplete

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

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

相关文章

2020年CSP-J认证 CCF非专业级别软件能力认证第一轮真题-单项选择题解析

2020 CCF认证第一轮&#xff08;CSP-J&#xff09;真题 一、单项选择题 (共15题&#xff0c;每2分&#xff0c;共30分;每题有且有一个正确选项&#xff09; 1、在内存储器中每个存储单元都被赋予一个唯一的序号,称为 A、下标 B、序号 C、地址 D、编号 答案&#xff1a;C…

当618成“抢人大战”,知道“怎么抢”才能“抢得到”

文 | 螳螂观察 作者 | 易不二 今年618对很多平台来说都意义非凡。 尤其是最具主场优势的阿里、京东而言&#xff0c;更是一场硬仗&#xff1a;阿里“16N”组织架构调整后&#xff0c;淘天的第一次大促&#xff0c;且还恰逢也淘宝20周年&#xff1b;京东换帅、CEO许冉第一次接…

华为OD机试真题 Java 实现【素数伴侣】【2023 B卷 100分】,附详细解题思路

一、题目描述 若两个正整数的和为素数&#xff0c;则这两个正整数称之为“素数伴侣”&#xff0c;如2和5、6和13&#xff0c;它们能应用于通信加密。现在密码学会请你设计一个程序&#xff0c;从已有的 N &#xff08; N 为偶数&#xff09;个正整数中挑选出若干对组成“素数伴…

13.常用类|Java学习笔记

文章目录 包装类包装类型和String类型的相互转换Integer类和Character类的常用方法Integer创建机制&面试题 String类创建String对象的两种方式和区别字符串的特性String类的常用方法 StringBuffer类String和StringBuffer相互转换StringBuffer常用方法 StringBuilder类Strin…

Java实训日志01

文章目录 一、安装录屏软件&#xff08;一&#xff09;下载软件&#xff08;二&#xff09;安装软件 二、使用录屏软件三、安装XMind软件&#xff08;一&#xff09;下载XMind软件&#xff08;二&#xff09;安装XMind软件 四、创建思维导图&#xff08;一&#xff09;启动XMin…

如何评价广告营销效果

广告营销的目的 广义上的营销&#xff0c;2个主要目的&#xff1a;达成销售和建设品牌&#xff0c;其实后者的终极目的还是前者。销售是短期达成&#xff0c;品牌建设是长期管理。 广告是营销的一部分&#xff0c;广告的效果评估也应该从目的出发探寻方法。 网络广告的特性 网…

2023Fiddler学习笔记 -- 状态栏及辅助选项卡

接上节课内容 2023Fiddler抓包学习笔记 -- 环境配置及工具栏介绍 2023Fiddler抓包学习笔记 -- 如何在会话窗口添加ip列 一、状态栏 1、上面黑色的框框&#xff0c;可以输入相关命令实现操作&#xff0c;比如&#xff1a;bpu baidu&#xff0c;只要url里包含baidu的网站都会被…

Socket 传情:用 Python 编织 TCP 网络

文章目录 参考描述TCP 服务器端与 TCP 客户端通信的基本流程服务器端客户端 使用 socket 实现 TCP 服务器端实现监听套接字socket.socket()Socket().bind()IP 地址的选择本地回环地址某一特定 IP 地址空字符串 Socket().listen()监听套接字的实现 实现连接套接字Socket().accep…

【I2C】Linux使用GPIO模拟I2C

文章目录 1. I2C GPIO系统架构简介2. 如何使能I2C GPIO驱动2.1 config配置2.2 dts配置2.3 测试验证 3. 简单分析i2c-gpio.c驱动3.1 解析设备树3.2 配置SDA和SCL3.3 注册到i2c-algo-bit.c 4. 简单分析i2c-algo-bit.c驱动4.1 提供I2C通信时的算法4.2 注册Adapter 5. 参考资料 1. …

翻车了,被读者找出 BUG

大家好呀&#xff0c;我是小楼。 本文是上篇文章《使用增强版 singleflight 合并事件推送&#xff0c;效果炸裂&#xff01;》的续集&#xff0c;没看过前文必须要先看完才能看本文&#xff0c;实在不想看&#xff0c;拉到文章末尾&#xff0c;给我点个赞再退出吧~Doge 上篇文…

如何进行App性能测试?SoloPi是最佳选择!

目录 引言 SoloPi简介 SoloPi特点 SoloPi的主要功能 下载SoloPi 安装SoloPi 使用SoloPi进行性能测试 性能数据查看与记录 环境加压 响应耗时计算工具 注意事项 Solopi提供的各项性能指标介绍 引言 大家好&#xff01;我是凡哥。 今天我想跟你们分享一下如何进行A…

Python的接口自动化-读写excel文件

目录 引言 一、xlrd、xlwt以及xlutils安装 二、xlrd操作excel文件的数据读取 三、xlwt向excel文件写入数据 四、xlutils操作excel文件 五、封装操作excel读和写的类 引言 使用python进行接口测试时常常需要接口用例测试数据、断言接口功能、验证接口响应状态等&#xff0…

Mysql升级8.0后日期类型兼容性问题

背景 最近对原有项目数据库进行升级&#xff0c;从MySQL 5.7 升级到8.0&#xff0c;因此项目种的驱动程序也要做相应升级。 问题 一、 升级后报&#xff1a;java.time.LocalDateTime cannot be cast to java.util.Date 该问题是因为代码中使用Map类型获取查询返回值&#xf…

lightdb检测不兼容工具CheckUnsupportOracle使用说明

oracle有很多特性在lightdb无法使用&#xff0c;使用该工具可以即时扫描某个在线数据库或本地文件夹中有哪些不兼容特性&#xff0c;-a -x对oracle不兼容特性有所区别&#xff0c;扫描范围可参考后续表格。 0、使用限制 默认扫描存储过程&#xff0c;函数及包默认仅支持oracl…

19JS10——预解析

文章目录 一、预解析二、变量预解析和函数预解析三、预解析案例1、案例1&#xff1a;结果是几&#xff1f;2、案例2&#xff1a;结果是几&#xff1f;3、案例3&#xff1a;结果是几&#xff1f;4、案例4&#xff1a;结果是几&#xff1f; 目标&#xff1a; 1、预解析 2、变量预…

电脑连上wifi但显示无网解决方案分享,轻松搞定电脑上网问题

有的时候我们会遇到这样的问题&#xff1a;电脑明明连接了网络但仍不能上网&#xff0c;并且出现错误提示“WiFi已连接但没有互联网”。遇到这种情况&#xff0c;我们可以先使用另一台设备访问网络&#xff0c;看看你能否正常上网。如果也不能上网&#xff0c;可以尝试重新启动…

七牛云存储开启referer防盗链后,微信小程序访问提示403

点击七牛云存储存储桶绑定的加速域名 配置站点域名和微信小程序域名 是否允许空Referer一定要打开&#xff0c;否则小程序上的视频或图片访问时将提示403

# 如何在Git上更改本地分支名称和远程分支名称

有时候我们需要修改git分支名称&#xff0c;例如不合理的分支名称。本篇文章分享了如何轻松地修改Git本地分支名称和修改远程分支名称。 在Git中&#xff0c;通常使用分支来使开发与您的主要工作流程分开。在软件工程团队中&#xff0c;通常要实施特定的工作流程。例如&#x…

[C语言实现]数据结构之《关于我转生成队列这档事》

&#x1f970;作者: FlashRider &#x1f30f;专栏: 数据结构 &#x1f356;知识概要&#xff1a;详解队列的概念、顺序队列和链式队列的优点和缺点&#xff0c;以及代码实现。 目录 什么是队列&#xff1f; 选择什么结构来实现队列&#xff1f; 链式队列的实现 队列的结构…

在UE中使用Stencil功能

Stencil是指利用深度buffer的后8位数据进行bit mask信息的绘制&#xff0c;从而制作类似角色mask遮罩等效果&#xff0c;下面就在UE中进行制作。 1.首先在Project Settings项目设置中开启stencil&#xff0c;搜索stencil关键字在Custom Depth Stencil Pass选项中设置为Enabled…