Android Radio2.0——设置广播配置标志(一)
在 Android Radio 中,RDS (Radio Data System) 是一种在调频(FM)广播信号中嵌入数字信息的技术,它可以携带额外的数据信息,如电台名称、节目信息等。
一、广播配置设置
在介绍 RDS 广播配置设置前我们先来了解一些常见的 RDS 代码及其含义:
- AF (Alternative Frequencies):表示备用频率。当主频率受到干扰或者信号质量下降时,接收器可以自动切换到这些备用频率上继续收听同一个广播节目。
- REG (Regional Information):区域信息。用于发送与特定地区相关的文本信息,例如交通状况或天气预报等。
- TP (Traffic Programme):交通节目标志。如果一个电台主要播放交通信息,它会发送这个标志。接收器可以根据这个标志来决定是否锁定该电台。
- TA (Traffic Announcement):交通公告。当有重要的交通信息(如道路封闭、事故等)时,电台会发送这个标志。接收器可以被设置为自动切换到发送 TA 标志的电台。
这些信息都是通过 RDS 系统传输的附加数据,使得 FM 收音机可以提供更多的功能和服务,例如自动频率切换、交通信息提示等。这些功能增强了用户体验,并提供了更加丰富的广播内容交互方式。
1、RDS配置
private final AtomicBoolean isTAOpen = new AtomicBoolean(false);
private final AtomicBoolean isTPOpen = new AtomicBoolean(false);private static final String DEFAULT_SETTINGS = "[0,0,1,0]";
private static final int STATUS_OPEN = 1;private final RadioTuner mRadioTuner;// 接口调用
setRadioRDSSettings(DEFAULT_SETTINGS)/*** 配置Radio相关功能*/
private void setRadioRDSSettings(String value) {int[] settings = StringUtils.toIntArray(value);//{AF,REG,TP,TA}boolean isAFOpen = settings[0] == STATUS_OPEN;boolean isREGOpen = settings[1] == STATUS_OPEN;isTAOpen.set(settings[2] == STATUS_OPEN);isTPOpen.set(settings[3] == STATUS_OPEN);try {// 设置RDS配置mRadioTuner.setConfigFlag(RadioManager.CONFIG_RDS_AF, isAFOpen);mRadioTuner.setConfigFlag(RadioManager.CONFIG_RDS_REG, isREGOpen);} catch (Exception e) {e.printStackTrace();}
}
这里主要是通过 setConfigFlag 函数设置 AF 和 REG 的开关状态。对于 TP 和 TA 的开关状态设置我们放到后面讲。
2、AtomicBoolean
可以发现上面函数中 TP 和 TA 开关状态使用了 AtomicBoolean 数据类型,它与 Boolean 类型的数据有什么区别是什么?这里我们通过与基本类型 Boolean 进行对比来了解 AtomicBoolean类型。
Boolean
- 数据类型:Java 中 boolean 基本类型的包装类。Boolean 对象可以存储 true 或 false 值,并且提供了与基本类型 boolean 相关的一些静态方法。
- 线程安全性:不具备任何内置的线程安全机制。如果你需要在线程间共享一个 Boolean 对象,并且多个线程可能会修改它的值,你需要自己负责同步(例如使用 synchronized 关键字或其他并发工具)。
- 方法和功能:提供了基本的操作,如 toString()、equals()、hashCode() 等,以及一些静态工厂方法如 valueOf()、parseBoolean() 等。
- 性能:需要在多线程环境中保证线程安全,通常需要使用同步机制,这可能会引入性能开销。
- 使用场景:适合于单线程环境或已经妥善处理了同步问题的多线程环境。
AtomicBoolean
- 数据类型:java.util.concurrent.atomic 包中的一个类,它是 Boolean 类型的一个变种,专为高并发环境设计。
- 线程安全性:提供了原子操作的支持,这意味着它可以在线程安全的方式下更新其值。AtomicBoolean 内部使用了 CAS(Compare and Swap)算法来保证更新操作的原子性,因此不需要显式的同步机制就可以安全地在线程间共享和修改。
- 方法和功能:提供了一些专门用于原子操作的方法,如 get()、compareAndSet()、getAndSet() 等,这些方法允许你在不担心线程竞争的情况下读取和修改 AtomicBoolean 的值。
- 性能:由于 AtomicBoolean 内置了原子操作的支持,因此在高并发环境下通常比手动同步的 Boolean 对象更高效。
- 使用场景:适合于高并发环境,特别是当多个线程需要并发访问和修改一个布尔值时。
综上所述 AtomicBoolean 是专门为解决多线程环境下的布尔值更新问题而设计的,它提供了线程安全的原子操作,使得在并发编程中更加方便和高效。而 Boolean 则是一个普通的包装类,适用于不需要考虑线程安全的场景。
3、配置类型
源码位置:/frameworks/base/core/java/android/hardware/radio/RadioManager.java
- CONFIG_FORCE_MONO:强制接收单声道音频流。在模拟广播(如AM/FM)中,当接收条件较差时,可以通过将立体声通道合并为单声道来改善接收效果。
- CONFIG_FORCE_ANALOG:强制使用模拟播放。用户可以选择禁用数字播放(如 FM HD Radio 或混合 FM/DAB)。这是一种用户选择,不反映 HAL 实现中的数字-模拟切换状态。
- CONFIG_FORCE_DIGITAL:强制使用数字播放。用户可以选择禁用在接收条件较差时发生的数字-模拟切换。在这种模式下,如果数字信号不可用,广播将保持静音,而不是切换到模拟频道。
- CONFIG_RDS_AF:启用 RDS 备选频率(Alternative Frequencies)。如果当前调谐的 RDS 电台在多个频道上广播,接收器将自动切换到最佳可用的备选频道。
- CONFIG_RDS_REG:启用 RDS 区域锁定(region-specific program lock-down)。允许用户在进入其他区域时锁定当前区域的广播内容。
- CONFIG_DAB_DAB_LINKING:启用 DAB-DAB 硬链接和隐式链接(相同内容)。在 DAB 广播中,链接两个具有相同内容的频道。
- CONFIG_DAB_FM_LINKING:启用 DAB-FM 硬链接和隐式链接(相同内容)。 在 DAB 和 FM 广播之间链接具有相同内容的频道。
- CONFIG_DAB_DAB_SOFT_LINKING:启用 DAB-DAB 软链接(相关内容)。在 DAB 广播中,链接两个具有相关内容的频道。
- CONFIG_DAB_FM_SOFT_LINKING:启用 DAB-FM 软链接(相关内容)。在 DAB 和 FM 广播之间链接具有相关内容的频道。
这里的 DAB 广播是一种完全数字化的广播技术,旨在替代传统的模拟广播(如 AM/FM),它不仅提供了更高品质的音频和更丰富的多媒体服务,同时还包括文本信息、静止图片、甚至低带宽视频。甚至可以提供接近 CD 质量的声音,并且具有更强的抗干扰能力和更好的移动接收性能。
二、接口调用
1、RadioTuner
源码位置:/frameworks/base/core/java/android/hardware/radio/RadioTuner.java
public void setConfigFlag(@RadioManager.ConfigFlag int flag, boolean value) {throw new UnsupportedOperationException();
}
这里如果传入的标志类型不适用会抛出 IllegalStateException。对于 RadioTuner 类提供了 setConfigFlag() 对应的接口,这里与前面的流程基本相同,TunerAdapter 实现了该接口。
2、TunerAdapter
源码位置:/frameworks/base/core/java/android/hardware/radio/TunerAdapter.java
class TunerAdapter extends RadioTuner {@NonNull private final ITuner mTuner;……@Overridepublic void setConfigFlag(@RadioManager.ConfigFlag int flag, boolean value) {try {mTuner.setConfigFlag(flag, value);} catch (RemoteException e) {throw new RuntimeException("service died", e);}}
}
这里同样通过 ITuner AIDL 接口调用 /hal1/Tuner.java 或 /hal2/TunerSession.java,后面的文章我们将不会再去关注 hal1 部分的代码。
3、TunerSession
源码位置:/frameworks/base/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
class TunerSession extends ITuner.Stub {private final ITunerSession mHwSession;@Overridepublic void setConfigFlag(int flag, boolean value) throws RemoteException {Slog.v(TAG, "setConfigFlag " + ConfigFlag.toString(flag) + " = " + value);synchronized (mLock) {checkNotClosedLocked();// 调用 HAL 层接口int halResult = mHwSession.setConfigFlag(flag, value);Convert.throwOnError("setConfigFlag", halResult);}}
}
这里主要通过 mHwSession 调用硬件抽象层(HAL)的 setConfigFlag 方法。
4、TunerSession(HAL)
TunerSession.h
源码位置:/hardware/interfaces/broadcastradio/2.0/default/TunerSession.h
struct TunerSession : public ITunerSession {virtual Return<Result> setConfigFlag(ConfigFlag flag, bool value);
}
可以看到 Hal 层的 TunerSession 继承 ITunerSession,并实现对应的 setConfigFlag() 函数。
TunerSession.cpp
源码位置:/hardware/interfaces/broadcastradio/2.0/default/TunerSession.cpp
Return<Result> TunerSession::setConfigFlag(ConfigFlag flag, bool value) {LOG(VERBOSE) << __func__ << " " << toString(flag) << " " << value;return Result::NOT_SUPPORTED;
}
可以看到这段代码只是一个占位符实现,表明当前 TunerSession 类不支持设置配置标志的功能。如果需要实现具体的配置逻辑,可以在 setConfigFlag 方法中添加相应的代码。如果确实不支持该功能,则可以保留当前返回 Result::NOT_SUPPORTED 的实现。
因此,如果需要实现对应功能,需要自行实现 Hal 层 TunerSession 中的对应函数,对于 Hal 层并不是我们主要关注的只是点,这里我们简单介绍一下思路即可:
- 上面 setConfigFlag() 函数中调用 TunerHwAdapter 中的对应函数,其中 TunerHwAdapter 自定义接口类。
- TunerHwAdapter 中的 setConfigFlag() 函数调用 RdsHwService/DabHwService 中的对应函数,RdsHwService/DabHwService 是我们创建的与对应硬件交互的类。