Android Radio2.0——交通公告状态设置(二)
通过前面的学习,我们知道在 Radio 广播中,交通公告(Traffic Announcement, TA)是一个比较重要的概念,它和交通广播(Traffic Radio)是相关的概念,但它们并不完全相同。
一、简介
1、概念介绍
交通公告
- 定义:交通公告是指在广播中插入的特别信息,通常是关于交通状况的重要通知,比如交通事故、道路封闭、交通拥堵等。
- 目的:目的是让驾驶者及时了解道路上的情况,以便调整行驶路线,避免拥堵或事故区域。
- 触发机制:交通公告可以通过手动触发(例如,广播员决定播放一条交通信息),也可以通过自动化系统触发(例如,交通监控系统检测到事故后自动发送信息)。
- 技术实现:在RDS广播中,TA标志用来标记包含交通信息的广播。当接收设备设置了接收TA信息时,会优先播放这些信息,即使打断了当前正在播放的节目。
交通广播
- 定义:交通广播是指专门提供交通信息的广播频道或节目。这些频道或节目专注于播报交通状况、路况信息以及其他与驾驶者相关的资讯。
- 目的:提供持续的交通信息服务,帮助驾驶者规划行程,避开拥堵路段。
- 内容:除了交通公告外,交通广播还可能包括天气预报、道路维修信息、交通法规更新等。
2、关系
- 包含关系:交通广播可能会包含交通公告作为其中的一部分内容,特别是在交通状况发生变化时,交通广播会插播交通公告。
- 技术应用:在RDS广播中,交通公告(TA)是一个特定的功能,而在DAB广播中也有类似的交通信息功能,但它们是通过不同的技术实现的。
3、示例
- RDS 广播中的 TA:当你在听音乐时,如果 FM 广播电台检测到有重要的交通信息,它会中断当前的音乐播放,插入一段交通公告,然后继续播放原来的节目。
- DAB 广播中的交通信息:在 DAB 广播中,交通信息同样可以被标记出来,并且在接收设备上可以设置优先显示或播放这些信息。
总结来说,交通公告是交通广播中的一种特定类型的信息,而交通广播则是一个更广泛的概念,涵盖了所有与交通信息相关的广播内容。
二、交通公告设置
1、接口封装
private static final String NOTIFY_HAL_DAB_TA_FLAG ="com.hal.operations.dab_ta_on";
private static final String NOTIFY_HAL_RDS_TA_FLAG ="com.hal.operations.rds_ta_on";
private final AtomicBoolean isTAOpen = new AtomicBoolean(false);
private final RadioTuner mRadioTuner;
private void setRadioTASettings(String value, boolean dabTA) {
int[] settings = StringUtils.toIntArray(value);
isTAOpen.set(settings[2] == STATUS_OPEN);
dabTA
// 设置FM广播和DAB广播中的交通公告状态
String valueRDS = rdsTa ? "1" : "0";
String valueDAB = dabTa ? "1" : "0";
Map<String,String> map = new HashMap<>();
map.put(NOTIFY_HAL_RDS_TA_FLAG,valueRDS);
map.put(NOTIFY_HAL_DAB_TA_FLAG,valueDAB);
mRadioTuner.setParameters(map);
}
可以看到,这里同时设置了 RDS 和 DAB 广播的交通公告状态,通过 RadioTuner 的 setParameters() 放法实现,对于该方法应该并不陌生,在前面的文章中有提到过。而对应的 NOTIFY_HAL_DAB_TA_FLAG 和 NOTIFY_HAL_RDS_TA_FLAG 参数是与底层接口定义好的。
2、设备支持
在一个设备上收听 RDS 和 DAB 广播取决于设备的硬件能力和软件支持。RDS(Radio Data System)主要用于传统的调频(FM)广播,而 DAB(Digital Audio Broadcasting)则是数字广播的一种形式。两者的技术标准不同,因此需要不同的硬件来支持。
硬件支持
- 对于 RDS 收听,设备需要有一个支持 RDS 的 FM 调谐器。
- 对于 DAB 收听,设备需要有一个 DAB 调谐器。
实际应用
在实际应用中,很少有设备会在同一时刻收听两个广播源,因为这通常没有必要且会增加功耗和复杂性。然而,现代的一些高端设备如车载娱乐系统可能会支持同时接收多种广播信号,并允许用户在不同的广播源之间切换。这需要在一个设备上配备至少两个调谐器,一个用于 FM/RDS,另一个用于 DAB。同时操作系统和应用软件支持同时管理多个调谐器,并且能够同时处理两种广播数据流。
广播现状
- RDS 在欧洲尤其流行,许多国家的广播电台都支持 RDS。在北美的美国和加拿大,RDS 也被广泛采用。在亚洲的日本、韩国等地也有一定的应用。
- DAB 在欧洲的挪威、英国、德国等国得到了推广和使用。在亚洲韩国、新加坡等国家也有应用。其他地区的澳大利亚、加拿大等也在推行 DAB 技术。
RDS 和 DAB 技术都在不断发展并且在全球范围内得到应用。特别是在欧洲,这两种技术都已经相当成熟,并且有很多广播电台支持这两种技术。 在中国,DAB 技术的应用相对较少,主要是由于中国有自己的数字广播标准——CMMB(China Multimedia Mobile Broadcasting)。不过,随着全球化的推进和技术的进步,DAB 在中国的应用也在逐渐增加。
三、设置流程分析
1、RadioTuner
源码位置:/frameworks/base/core/java/android/hardware/radio/RadioTuner.java
/**
* 用于设置供应商特定参数值的通用方法
* Framework不解释参数,它们以不透明的方式在供应商应用程序和HAL之间传递。
*
* @param 参数特定于供应商的键值对
* @return 正在设置参数的操作完成状态
*/
public @NonNull Map<String, String> setParameters(@NonNull Map<String, String> parameters) {
throw new UnsupportedOperationException();
}
该方法用于设置供应商特定的参数值,并将这些参数以不透明的方式传递给 HAL(硬件抽象层)。 并且该函数的实现也是在 TunerAdapter 中。
2、TunerAdapter
源码位置:/frameworks/base/core/java/android/hardware/radio/TunerAdapter.java
class TunerAdapter extends RadioTuner {
@NonNull private final ITuner mTuner;
……
@Override
public @NonNull Map<String, String> setParameters(@NonNull Map<String, String> parameters) {
try {
return mTuner.setParameters(Objects.requireNonNull(parameters));
} catch (RemoteException e) {
throw new RuntimeException("service died", e);
}
}
}
这里通过 ITuner 接口调用 TunerSession 中的 setParameters() 函数。
3、TunerSession
源码位置:/frameworks/base/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java
class TunerSession extends ITuner.Stub {
private final ITunerSession mHwSession;
……
@Override
public Map setParameters(Map parameters) {
synchronized (mLock) {
// 检查关闭状态
checkNotClosedLocked();
// 参数转换并调用底层对应函数
return Convert.vendorInfoFromHal(Utils.maybeRethrow(() ->
mHwSession.setParameters(Convert.vendorInfoToHal(parameters))));
}
}
}
这里通过 mHwSession 调用硬件抽象层(HAL)的 setParameters() 方法。
4、TunerSession(Hal)
TunerSession.h
源码位置:/hardware/interfaces/broadcastradio/2.0/default/TunerSession.h
struct TunerSession : public ITunerSession {
virtual Return<void> setParameters(const hidl_vec<VendorKeyValue>& parameters,
setParameters_cb _hidl_cb) override;
}
可以看到 Hal 层的 TunerSession 继承 ITunerSession,并实现对应的 setParameters() 函数。
TunerSession.cpp
源码位置:/hardware/interfaces/broadcastradio/2.0/default/TunerSession.cpp
Return<void> TunerSession::setParameters(const hidl_vec<VendorKeyValue>& /* parameters */,
setParameters_cb _hidl_cb) {
_hidl_cb({});
return {};
}
这里又是一个空的函数处理,那么 Android 源码为什么要这样设计,而不是实现具体功能。可能处于以下几点考虑:
- 框架设计的灵活性:在框架设计初期,可能还没有具体的实现细节,但需要提前定义好接口以确保后续实现的一致性和可扩展性。这样的设计可以保证接口的一致性和稳定性,即使当前没有具体实现,也可以在后续开发中逐步完善。
- 占位符实现:在某些情况下,为了保证编译通过或者测试框架的完整性,会先提供一个简单的占位符实现。这种做法可以避免编译错误或运行时异常,确保代码能够正常构建和运行。
- 预留接口:在某些模块中,某些功能可能暂时不需要实现,但未来可能会添加新的功能。预留接口可以方便地在未来添加具体实现,而不影响现有代码结构。
- 异步回调处理:对于异步回调函数,有时会在初始实现中提供一个空的回调处理,以确保回调机制的正确性。空的回调处理可以确保不会出现未处理的异常情况,并且可以在后续逐步完善具体逻辑。
而这里大概率是考虑各个硬件厂商属性值的不确定性,所以处理逻辑会差别很大,对于这里的实现我们还是不做过多介绍,简单看一下。
Return<void> TunerSession::setParameters(const hidl_vec<VendorKeyValue>& parameters,
setParameters_cb _hidl_cb) {
ALOGI("%s(%s)", __func__, toString(parameters).c_str());
std::vector<VendorKeyValue> result;
// 处理属性值
for (auto parameter:parameters) {
// 初步处理属性key和value
parameter.key
parameter.value
}
// 同样调用TunerHwAdapter中的对应函数
mTunerHwAdapter->setParameters(parameters,result);
_hidl_cb(result);
return {};
}
最终在 TunerHwAdapter 中在分别调用 AmfmHwService、RdsHwService 或 DabHwService 中的对应方法,这里识别传入的属性值来与硬件进行最终交互完成功能的设置。
对于 setParameters() 的功能,不仅仅是这里介绍的交通公告设置,该函数其实就是应用层与 Hal 交互的一个接口,通过不同的 key 和 value 可以实现任何功能。包括通知 Hal 层初始化、播放/暂停、申请/释放焦点等等。