调谐器框架

对于 Android 11 或更高版本,您可以使用 Android 调谐器框架来传送音视频 (A/V) 内容。该框架使用来自供应商的硬件流水线,使其同时适用于低端和高端 SoC。该框架提供一种安全的方式来传送受可信执行环境 (TEE) 和安全媒体路径 (SMP) 保护的 A/V 内容,以便在高度受限的内容保护环境中使用。

调谐器与 Android CAS 之间的标准化接口加快了调谐器供应商与 CAS 供应商之间的集成。调谐器接口与 MediaCodecAudioTrack 配合使用,为 Android TV 构建全球统一的解决方案。调谐器接口支持符合主流广播标准的数字电视和模拟电视。

组件

对于 Android 11,专为电视平台设计了以下三个组件。

  • 调谐器 HAL:框架与供应商之间的接口
  • 调谐器 SDK API:框架与应用之间的接口
  • 调谐器资源管理器 (TRM):用于协调调谐器硬件资源

对于 Android 11,以下组件得到增强。

  • CAS V2
  • TvInputService,即电视输入服务 (TIS)
  • TvInputManagerService,即电视输入管理器服务 (TIMS)
  • MediaCodec,即媒体编解码器
  • AudioTrack,即音轨
  • MediaResourceManager,即媒体资源管理器 (MRM)

调谐器框架组件流程图。

图 1. Android TV 组件之间的交互

功能

前端支持以下 DTV 标准。

  • ATSC
  • ATSC3
  • DVB C/S/T
  • ISDB S/S3/T
  • 模拟

Android 12 中使用调谐器 HAL 1.1 或更高版本的前端支持以下 DTV 标准。

  • DTMB

多路分配器 (Demux) 支持以下流媒体协议。

  • 传输流 (TS)
  • MPEG 媒体传输协议 (MMTP)
  • 互联网协议 (IP)
  • 类型长度值 (TLV)
  • ATSC 链路层协议 (ALP)

解扰器 (Descrambler) 支持以下各项内容保护。

  • 安全的媒体路径
  • 清晰的媒体路径
  • 安全的本地记录
  • 安全的本地播放

调谐器 API 支持以下用例。

  • 扫描
  • 直播
  • 播放
  • 录制

调谐器、MediaCodecAudioTrack 支持以下数据流模式。

  • 具有清晰内存缓冲区的 ES 载荷
  • 具有安全内存句柄的 ES 载荷
  • 透传

整体设计

调谐器 HAL 在 Android 框架与供应商硬件之间定义。

  • 描述框架对供应商的要求,以及供应商为实现要求可能采取的做法。
  • 通过 IFrontendIDemuxIDescramblerIFilterIDvrILnb 接口将前端、多路分配器和解扰器的功能导出到框架中。
  • 包含用于将调谐器 HAL 与其他框架组件(如 MediaCodecAudioTrack)相集成的函数。

创建了一个调谐器 Java 类和一个原生类。

  • 借助调谐器 Java API,应用可以通过公共 API 访问调谐器 HAL。
  • 借助原生类,可以使用调谐器 HAL 进行权限控制和处理大量的录制或播放数据。
  • 原生调谐器模块是调谐器 Java 类与调谐器 HAL 之间的桥梁。

创建了一个 TRM 类。

  • 用于管理有限的调谐器资源,如前端、LNB、CAS 会话和来自 TV 输入 HAL 的 TV 输入设备。
  • 通过应用规则来从应用中回收不足的资源。默认规则是前台优先。

通过以下功能增强了 Media CAS 和 CAS HAL。

  • 针对不同的使用情况和算法分别打开不同的 CAS 会话。
  • 支持动态 CAS 系统,如 CCIAM 移除和插入。
  • 通过提供密钥令牌与调谐器 HAL 集成。

通过以下功能增强了 MediaCodecAudioTrack

  • 以安全的 A/V 内存作为内容输入。
  • 配置为在隧道式播放过程中执行硬件 A/V 同步。
  • 配置为支持 ES_payload 和透传模式。

调谐器 HAL 的整体设计。

图 2. 调谐器 HAL 中组件的示意图

整体工作流

以下图表显示了直播播放的调用序列。

设置

设置直播播放序列的示意图。

图 3. 设置直播播放的序列

处理 A/V

处理直播播放的 A/V 的示意图。

图 4. 处理直播播放的 A/V

处理扰码内容

处理直播播放的扰码内容的示意图。

图 5. 处理直播播放的扰码内容

处理 A/V 数据

处理直播播放的 A/V 数据的示意图。

图 6. 处理直播播放的 A/V

调谐器 SDK API

调谐器 SDK API 用于处理与调谐器 JNI、调谐器 HAL 和 TunerResourceManager 的交互。TIS 应用使用调谐器 SDK API 来访问调谐器资源和子组件,如过滤器和解扰器。前端和多路分配器是内部组件。

调谐器 SDK API 的流程图。

图 7. 与调谐器 SDK API 的交互

版本

从 Android 12 开始,调谐器 SDK API 支持调谐器 HAL 1.1 中的新功能,调谐器 HAL 1.1 是调谐器 1.0 的版本升级并向后兼容。

使用以下 API 检查正在运行的 HAL 版本。

  • android.media.tv.tuner.TunerVersionChecker.getTunerVersion()

新版 Android 12 API 的文档中介绍了所需的最低 HAL 版本。

套餐

调谐器 SDK API 提供以下四个软件包。

  • android.media.tv.tuner
  • android.media.tv.tuner.frontend
  • android.media.tv.tuner.filter
  • android.media.tv.tuner.dvr

调谐器 SDK API 软件包的流程图。

图 8. 调谐器 SDK API 软件包

Android.media.tv.tuner

该调谐器软件包是使用调谐器框架的入口点。TIS 应用使用该软件包通过指定初始设置和回调来初始化和获取资源实例。

  • tuner():通过指定 useCasesessionId 参数来初始化调谐器实例。
  • tune():获取前端资源,并通过指定 FrontendSetting 参数进行调谐。
  • openFilter():通过指定过滤器类型获取过滤器实例。
  • openDvrRecorder():通过指定缓冲区大小获取录制实例。
  • openDvrPlayback():通过指定缓冲区大小获取播放实例。
  • openDescrambler():获取解扰器实例。
  • openLnb():获取内部 LNB 实例。
  • openLnbByName():获取外部 LNB 实例。
  • openTimeFilter():获取时间过滤器实例。

该调谐器软件包提供过滤器、DVR 和前端包未涵盖的功能。下面列出了这些功能。

  • cancelTuning
  • scan / cancelScanning
  • getAvSyncHwId
  • getAvSyncTime
  • connectCiCam1 / disconnectCiCam
  • shareFrontendFromTuner
  • updateResourcePriority
  • setOnTuneEventListener
  • setResourceLostListener

Android.media.tv.tuner.frontend

该前端软件包包含与前端相关的设置、信息、状态、事件和功能的集合。

FrontendSettings 由以下类针对不同的 DTV 标准衍生。

  • AnalogFrontendSettings
  • Atsc3FrontendSettings
  • AtscFrontendSettings
  • DvbcFrontendSettings
  • DvbsFrontendSettings
  • DvbtFrontendSettings
  • Isdbs3FrontendSettings
  • IsdbsFrontendSettings
  • IsdbtFrontendSettings

从 Android 12 开始,调谐器 HAL 1.1 或更高版本支持以下 DTV 标准。

  • DtmbFrontendSettings

FrontendCapabilities 由以下类针对不同的 DTV 标准衍生。

  • AnalogFrontendCapabilities
  • Atsc3FrontendCapabilities
  • AtscFrontendCapabilities
  • DvbcFrontendCapabilities
  • DvbsFrontendCapabilities
  • DvbtFrontendCapabilities
  • Isdbs3FrontendCapabilities
  • IsdbsFrontendCapabilities
  • IsdbtFrontendCapabilities

从 Android 12 开始,调谐器 HAL 1.1 或更高版本支持以下 DTV 标准。

  • DtmbFrontendCapabilities

FrontendInfo 可检索前端的信息。FrontendStatus 可检索前端的当前状态。OnTuneEventListener 可监听前端的事件。TIS 应用使用 ScanCallback 来处理来自前端的扫描消息。

频道扫描

为了设置 TV,应用会扫描可能的频率,并构建供用户访问的频道组。TIS 可能会使用 Tuner.tuneTuner.scan(BLIND_SCAN)Tuner.scan(AUTO_SCAN) 来完成频道扫描。

如果 TIS 具有信号的准确传送信息,如频率、标准(例如 T/T2、S/S2)以及其他必要信息(例如 PLD ID),则建议使用 Tuner.tune 作为更快的选择。

当用户调用 Tuner.tune 时,系统会执行以下操作:

  • TIS 会使用 Tuner.tuneFrontendSettings 填充必要的信息。
  • 如果信号锁定,HAL 会报告调谐 LOCKED 消息。
  • TIS 会使用 Frontend.getStatus 收集必要的信息。
  • TIS 会采用频率列表中的下一个可用频率。

TIS 会再次调用 Tuner.tune,直到用完所有频率。

在调谐期间,您可以调用 stopTune()close() 来暂停或结束 Tuner.tune 调用。

Tuner.scan(AUTO_SCAN)

如果 TIS 没有足够的信息来使用 Tuner.tune,但具有频率列表和标准类型(例如 DDB T/C/S),那么建议使用 Tuner.scan(AUTO_SCAN)

当用户调用 Tuner.scan(AUTO_SCAN) 时,系统会执行以下操作:

  • TIS 结合使用 Tuner.scan(AUTO_SCAN) 与填充了频率的 FrontendSettings

  • 如果信号锁定,HAL 会报告扫描 LOCKED 消息。HAL 可能还会报告其他扫描消息,以提供与信号有关的其他信息。

  • TIS 会使用 Frontend.getStatus 收集必要的信息。

  • TIS 会调用 Tuner.scan,供 HAL 使用下一个相同频率的设置。如果 FrontendSettings 结构为空,HAL 将使用下一个可用设置。否则,HAL 会使用 FrontendSettings 一次性扫描,并发送 END 表明扫描操作已完成。

  • TIS 会重复执行上述操作,直到用完所有频率设置。

  • HAL 会发送 END 表明扫描操作已完成。

  • TIS 会采用频率列表中的下一个可用频率。

TIS 会再次调用 Tuner.scan(AUTO_SCAN),直到用完所有频率。

在扫描期间,您可以调用 stopScan()close() 来暂停或结束扫描。

Tuner.scan(BLIND_SCAN)

如果 TIS 没有频率列表,且供应商 HAL 可以搜索用户指定前端的频率来获取前端资源,则建议使用 Tuner.scan(BLIND_SCAN)

  • TIS 会使用 Tuner.scan(BLIND_SCAN)。可以在 FrontendSettings 中指定起始频率,但 TIS 会忽略 FrontendSettings 中的其他设置。
  • 如果信号锁定,HAL 会报告扫描 LOCKED 消息。
  • TIS 会使用 Frontend.getStatus 收集必要的信息。
  • TIS 会再次调用 Tuner.scan 以继续扫描。(会忽略 FrontendSettings。)
  • TIS 会重复执行上述操作,直到用完所有频率设置。HAL 会逐步提高频率,而不需要 TIS 执行任何操作。HAL 会报告 PROGRESS

TIS 会再次调用 Tuner.scan(AUTO_SCAN),直到用完所有频率。HAL 会报告 END 以表明扫描操作已完成。

在扫描期间,您可以调用 stopScan()close() 来暂停或结束扫描。

TIS 扫描过程的流程图。

图 9. TIS 扫描的流程图

Android.media.tv.tuner.filter

过滤器软件包是过滤器操作以及配置、设置、回调和事件的集合。该软件包包含以下操作。如需查看完整的操作列表,请参阅 Android 源代码。

  • configure()
  • start()
  • stop()
  • flush()
  • read()

如需查看完整列表,请参阅 Android 源代码。

FilterConfiguration 派生自以下类。这些配置适用于主过滤器类型,它们指定该过滤器用于提取数据的协议。

  • AlpFilterConfiguration
  • IpFilterConfiguration
  • MmtpFilterConfiguration
  • TlvFilterConfiguration
  • TsFilterConfiguration

这些设置派生自以下类。这些设置适用于过滤器子类型,它们指定该过滤器可以排除的数据类型。

  • SectionSettings
  • AvSettings
  • PesSettings
  • RecordSettings
  • DownloadSettings

FilterEvent 派生自以下类以报告不同类型数据的事件。

  • SectionEvent
  • MediaEvent
  • PesEvent
  • TsRecordEvent
  • MmtpRecordEvent
  • TemiEvent
  • DownloadEvent
  • IpPayloadEvent

从 Android 12 开始,调谐器 HAL 1.1 或更高版本支持以下事件。

  • IpCidChangeEvent
  • RestartEvent
  • ScramblingStatusEvent
过滤器中的事件和数据格式
过滤条件类型 标志 事件 数据操作 数据格式
TS.SECTION
MMTP.SECTION
IP.SECTION
TLV.SECTION
ALP.SECTION
isRaw:
true
必需
DemuxFilterStatus::DATA_READY
DemuxFilterStatus::DATA_OVERFLOW

建议
DemuxFilterStatus::LOW_WATER
DemuxFilterStatus::HIGH_WATER
根据事件和内部时间表,运行
Filter.read(buffer, offset, adjustedSize) 一次或多次。

将数据从 HAL 的 MQ 复制到客户端缓冲区。
通过一个会话包将另一个组合好的会话包填充在 FMQ 中。
isRaw:
false
必需
DemuxFilterEvent::DemuxFilterSectionEvent[n]
DemuxFilterStatus::DATA_READY
DemuxFilterStatus::DATA_OVERFLOW

可选
DemuxFilterStatus::LOW_WATER
DemuxFilterStatus::HIGH_WATER
for i=0; i<n; i++
Filter.read(buffer, offset, DemuxFilterSectionEven[i].size)


将数据从 HAL 的 MQ 复制到客户端缓冲区。
TS.PES isRaw:
true
必需
DemuxFilterStatus::DATA_READY
DemuxFilterStatus::DATA_OVERFLOW

建议
DemuxFilterStatus::LOW_WATER
DemuxFilterStatus::HIGH_WATER
根据事件和内部时间表,运行
Filter.read(buffer, offset, adjustedSize) 一次或多次。

将数据从 HAL 的 MQ 复制到客户端缓冲区。
通过一个 PES 包将另一个组合好的 PES 包填充在 FMQ 中。
isRaw:
false
必需
DemuxFilterEvent::DemuxFilterPesEvent[n]
DemuxFilterStatus::DATA_READY
DemuxFilterStatus::DATA_OVERFLOW

可选
DemuxFilterStatus::LOW_WATER
DemuxFilterStatus::HIGH_WATER
for i=0; i<n; i++
Filter.read(buffer, offset, DemuxFilterPesEven[i].size)


将数据从 HAL 的 MQ 复制到客户端缓冲区。
MMTP.PES isRaw:
true
必需
DemuxFilterStatus::DATA_READY
DemuxFilterStatus::DATA_OVERFLOW

建议
DemuxFilterStatus::LOW_WATER
DemuxFilterStatus::HIGH_WATER
根据事件和内部时间表,运行
Filter.read(buffer, offset, adjustedSize) 一次或多次。

将数据从 HAL 的 MQ 复制到客户端缓冲区。
通过一个 MFU 包将另一个组合好的 MFU 包填充在 FMQ 中。
isRaw:
false
必需
DemuxFilterEvent::DemuxFilterPesEvent[n]
DemuxFilterStatus::DATA_READY
DemuxFilterStatus::DATA_OVERFLOW

可选
DemuxFilterStatus::LOW_WATER
DemuxFilterStatus::HIGH_WATER
for i=0; i<n; i++
Filter.read(buffer, offset, DemuxFilterPesEven[i].size)


将数据从 HAL 的 MQ 复制到客户端缓冲区。
TS.TS
必需
DemuxFilterStatus::DATA_READY
DemuxFilterStatus::DATA_OVERFLOW

建议
DemuxFilterStatus::LOW_WATER
DemuxFilterStatus::HIGH_WATER
根据事件和内部时间表,运行
Filter.read(buffer, offset, adjustedSize) 一次或多次。

将数据从 HAL 的 MQ 复制到客户端缓冲区。
将使用 ts 标头
过滤掉的 ts 填充在 FMQ 中。
TS.Audio
TS.Video
MMTP.Audio
MMTP.Video
isPassthrough:
true
可选
DemuxFilterStatus::DATA_READY
DemuxFilterStatus::DATA_OVERFLOW
客户端可以在收到 DemuxFilterStatus::DATA_READY 后启动 MediaCodec
客户端可以在收到 DemuxFilterStatus::DATA_OVERFLOW 后调用 Filter.flush
isPassthrough:
false
必需
DemuxFilterEvent::DemuxFilterMediaEvent[n]
DemuxFilterStatus::DATA_READY
DemuxFilterStatus::DATA_OVERFLOW

可选
DemuxFilterStatus::LOW_WATER
DemuxFilterStatus::HIGH_WATER
如需使用 MediaCodec,请执行以下操作:
for i=0; i<n; i++
linearblock = MediaEvent[i].getLinearBlock();
codec.startQueueLinearBlock(linearblock)
linearblock.recycle()


如需使用 AudioTrack 的直接音频,请执行以下操作:
for i=0; i<n; i++
audioHandle = MediaEvent[i].getAudioHandle();
audiotrack.write(encapsulated(audiohandle))
ION 内存中的 ES 数据或部分 ES 数据。
TS.PCR
IP.NTP
ALP.PTP
必需:不适用
可选:不适用
TS.RECORD 必需
DemuxFilterEvent::DemuxFilterTsRecordEvent[n]
RecordStatus::DATA_READY
RecordStatus::DATA_OVERFLOW
RecordStatus::LOW_WATER
RecordStatus::HIGH_WATER

可选
DemuxFilterStatus::DATA_READY
DemuxFilterStatus::DATA_OVERFLOW
DemuxFilterStatus::LOW_WATER
DemuxFilterStatus::HIGH_WATER
对于索引数据,请执行以下操作:
for i=0; i<n; i++
DemuxFilterTsRecordEvent[i];


对于录制的内容,请根据 RecordStatus::* 和内部时间表,执行以下操作之一:
  • 对存储空间运行 DvrRecord.write(adustedSize) 一次或多次。
    将数据从 HAL 的 MQ 转移至存储空间。
  • 对缓冲区运行 DvrRecord.write(buffer, adustedSize) 一次或多次。
    将数据从 HAL 的 MQ 复制到客户端缓冲区。
对于索引数据:包含在事件载荷中。

对于录制的内容:混合了 FMQ 中填充的 TS 流。
TS.TEMI 必需
DemuxFilterEvent::DemuxFilterTemiEvent[n]

可选
DemuxFilterStatus::DATA_READY
DemuxFilterStatus::DATA_OVERFLOW
DemuxFilterStatus::LOW_WATER
DemuxFilterStatus::HIGH_WATER
for i=0; i<n; i++
DemuxFilterTemiEvent[i];
MMTP.MMTP 必需
DemuxFilterStatus::DATA_READY
DemuxFilterStatus::DATA_OVERFLOW

建议
DemuxFilterStatus::LOW_WATER
DemuxFilterStatus::HIGH_WATER
根据事件和内部时间表,运行
Filter.read(buffer, offset, adjustedSize) 一次或多次。

将数据从 HAL 的 MQ 复制到客户端缓冲区。
将使用 mmtp 标头
过滤掉的 mmtp 填充在 FMQ 中。
MMTP.RECORD 必需
DemuxFilterEvent::DemuxFilterMmtpRecordEvent[n]
RecordStatus::DATA_READY
RecordStatus::DATA_OVERFLOW
RecordStatus::LOW_WATER
RecordStatus::HIGH_WATER

可选
DemuxFilterStatus::DATA_READY
DemuxFilterStatus::DATA_OVERFLOW
DemuxFilterStatus::LOW_WATER
DemuxFilterStatus::HIGH_WATER
对于索引数据,请执行以下操作: for i=0; i<n; i++
DemuxFilterMmtpRecordEvent[i];


对于录制的内容,请根据 RecordStatus::* 和内部时间表,执行以下操作之一:
  • 对存储空间运行 DvrRecord.write(adjustedSize) 一次或多次。
    将数据从 HAL 的 MQ 转移至存储空间。
  • 对缓冲区运行 DvrRecord.write(buffer, adjustedSize) 一次或多次。
    将数据从 HAL 的 MQ 复制到客户端缓冲区。
对于索引数据:包含在事件载荷中。

对于录制的内容:混合了 FMQ 中填充的录制的流。

如果供录制的过滤器源是 TLV.TLVIP.IP(透传),那么录制的流会包含 TLV 和 IP 标头。
MMTP.DOWNLOAD 必需
DemuxFilterEvent::DemuxFilterDownloadEvent[n]
DemuxFilterStatus::DATA_READY
DemuxFilterStatus::DATA_OVERFLOW

可选
DemuxFilterStatus::LOW_WATER
DemuxFilterStatus::HIGH_WATER
for i=0; i<n; i++ Filter.read(buffer, offset, DemuxFilterDownloadEvent[i].size)

将数据从 HAL 的 MQ 复制到客户端缓冲区。
使用一个 IP 下载包将另一个下载包填充在 FMQ 中。
IP.IP_PAYLOAD 必需
DemuxFilterEvent::DemuxFilterIpPayloadEvent[n]
DemuxFilterStatus::DATA_READY
DemuxFilterStatus::DATA_OVERFLOW

可选
DemuxFilterStatus::LOW_WATER
DemuxFilterStatus::HIGH_WATER
for i=0; i<n; i++ Filter.read(buffer, offset, DemuxFilterIpPayloadEvent[i].size)

将数据从 HAL 的 MQ 复制到客户端缓冲区。
使用一个 IP 载荷包将另一个 IP 载荷包填充在 FMQ 中。
IP.IP
TLV.TLV
ALP.ALP
isPassthrough:
true
可选
DemuxFilterStatus::DATA_READY
DemuxFilterStatus::DATA_OVERFLOW
过滤掉的协议子信息流将传递给过滤器链中的下一个过滤器。
isPassthrough:
false
必需
DemuxFilterStatus::DATA_READY
DemuxFilterStatus::DATA_OVERFLOW

建议
DemuxFilterStatus::LOW_WATER
DemuxFilterStatus::HIGH_WATER
根据事件和内部时间表,运行
Filter.read(buffer, offset, adjustedSize) 一次或多次。

将数据从 HAL 的 MQ 复制到客户端缓冲区。
将使用协议标头过滤掉的协议子信息流填充在 FMQ 中。
IP.PAYLOAD_THROUGH
TLV.PAYLOAD_THROUGH
ALP.PAYLOAD_THROUGH
可选
DemuxFilterStatus::DATA_READY
DemuxFilterStatus::DATA_OVERFLOW
过滤掉的协议载荷将传递给过滤器链中的下一个过滤器。
使用过滤器构建 PSI/SI 的示例流程

使用过滤器构建 PSI/SI 的示例流程。

图 10. 构建 PSI/SI 的流程

  1. 打开过滤器。

    Filter filter = tuner.openFilter(
      Filter.TYPE_TS,
      Filter.SUBTYPE_SECTION,
      /* bufferSize */1000,
      executor,
      filterCallback
    );
    
  2. 配置并启动过滤器。

    Settings settings = SectionSettingsWithTableInfo
        .builder(Filter.TYPE_TS)
        .setTableId(2)
        .setVersion(1)
        .setCrcEnabled(true)
        .setRaw(false)
        .setRepeat(false)
        .build();
      FilterConfiguration config = TsFilterConfiguration
        .builder()
        .setTpid(10)
        .setSettings(settings)
        .build();
      filter.configure(config);
      filter.start();
    
  3. 处理 SectionEvent

    FilterCallback filterCallback = new FilterCallback() {
      @Override
      public void onFilterEvent(Filter filter, FilterEvent[] events) {
        for (FilterEvent event : events) {
          if (event instanceof SectionEvent) {
            SectionEvent sectionEvent = (SectionEvent) event;
            int tableId = sectionEvent.getTableId();
            int version = sectionEvent.getVersion();
            int dataLength = sectionEvent.getDataLength();
            int sectionNumber = sectionEvent.getSectionNumber();
            filter.read(buffer, 0, dataLength); }
          }
        }
    };
    
在过滤器中使用 MediaEvent 的示例流程

在过滤器中使用 MediaEvent 的示例流程。

图 11. 在过滤器中使用 MediaEvent 的流程

  1. 打开、配置并启动 A/V 过滤器。
  2. 处理 MediaEvent
  3. 接收 MediaEvent
  4. 使线性块排队等候 codec
  5. 使用数据后释放 A/V 句柄。

Android.media.tv.tuner.dvr

DvrRecorder 提供以下录制方法。

  • configure
  • attachFilter
  • detachFilter
  • start
  • flush
  • stop
  • setFileDescriptor
  • write

DvrPlayback 提供以下播放方法。

  • configure
  • start
  • flush
  • stop
  • setFileDescriptor
  • read

DvrSettings 用于配置 DvrRecorderDvrPlaybackOnPlaybackStatusChangedListenerOnRecordStatusChangedListener 用于报告 DVR 实例的状态。

开始录制的示例流程

开始录制的示例流程。

图 12. 开始录制的流程

  1. 打开、配置并启动 DvrRecorder

    DvrRecorder recorder = openDvrRecorder(/* bufferSize */ 1000, executor, listener);
    DvrSettings dvrSettings = DvrSettings
    .builder()
    .setDataFormat(DvrSettings.DATA_FORMAT_TS)
    .setLowThreshold(100)
    .setHighThreshold(900)
    .setPacketSize(188)
    .build();
    recorder.configure(dvrSettings);
    recorder.attachFilter(filter);
    recorder.setFileDescriptor(fd);
    recorder.start();
    
  2. 接收 RecordEvent 并检索索引信息。

    FilterCallback filterCallback = new FilterCallback() {
      @Override
      public void onFilterEvent(Filter filter, FilterEvent[] events) {
        for (FilterEvent event : events) {
          if (event instanceof TsRecordEvent) {
            TsRecordEvent recordEvent = (TsRecordEvent) event;
            int tsMask = recordEvent.getTsIndexMask();
            int scMask = recordEvent.getScIndexMask();
            int packetId = recordEvent.getPacketId();
            long dataLength = recordEvent.getDataLength();
            // handle the masks etc. }
          }
        }
    };
    
  3. 初始化 OnRecordStatusChangedListener 并存储录制数据。

      OnRecordStatusChangedListener listener = new OnRecordStatusChangedListener() {
        @Override
        public void onRecordStatusChanged(int status) {
          // a customized way to consume data efficiently by using status as a hint.
          if (status == Filter.STATUS_DATA_READY) {
            recorder.write(size);
          }
        }
      };
    

调谐器 HAL

调谐器 HAL 遵循 HIDL 并定义了框架与供应商硬件之间的接口。供应商使用该接口实现调谐器 HAL,而框架使用该接口与调谐器 HAL 实现进行通信。

模块

调谐器 HAL 1.0

模块 基本控件 特定于模块的控件 HAL 文件
ITuner frontend(open, getIds, getInfo)openDemuxopenDescrambleropenLnbgetDemuxCaps ITuner.hal
IFrontend setCallbackgetStatusclose tunestopTunescanstopScansetLnb IFrontend.hal
IFrontendCallback.hal
IDemux close setFrontendDataSourceopenFilteropenDvrgetAvSyncHwIdgetAvSyncTimeconnect / disconnectCiCam IDemux.hal
IDvr closestartstopconfigure attach/detachFiltersflushgetQueueDesc IDvr.hal
IDvrCallback.hal
IFilter closestartstopconfiguregetId flushgetQueueDescreleaseAvHandlesetDataSource IFilter.hal
IFilterCallback.hal
ILnb closesetCallback setVoltagesetTonesetSatellitePositionsendDiseqcMessage ILnb.hal
ILnbCallback.hal
IDescrambler close setDemuxSourcesetKeyTokenaddPidremovePid IDescrambler.hal

调谐器 HAL 1.1(派生自调谐器 HAL 1.0)

模块 基本控件 特定于模块的控件 HAL 文件
ITuner getFrontendDtmbCapabilities @1.1::ITuner.hal
IFrontend tune_1_1scan_1_1getStatusExt1_1 link/unlinkCiCam @1.1::IFrontend.hal
@1.1::IFrontendCallback.hal
IFilter getStatusExt1_1 configureIpCidconfigureAvStreamTypegetAvSharedHandleconfigureMonitorEvent @1.1::IFilter.hal
@1.1::IFilterCallback.hal

调谐器 HAL 模块间的交互流程图。

图 13. 调谐器 HAL 模块间的交互示意图

过滤器关联

调谐器 HAL 支持过滤器关联,以使过滤器能够关联到多个层的其他过滤器。过滤器遵循以下规则。

  • 过滤器以树状形式关联,不允许使用闭合路径。
  • 根节点为多路分配器。
  • 过滤器独立运行。
  • 所有过滤器均开始获取数据。
  • 过滤器关联会刷新上一个过滤器。

以下代码块和图 14 展示了过滤多个层的示例。

demuxCaps = ITuner.getDemuxCap;
If (demuxCaps[IP][MMTP] == true) {
        ipFilter = ITuner.openFilter(<IP, ..>)
        mmtpFilter1 = ITuner.openFilter(<MMTP ..>)
        mmtpFilter2 = ITuner.openFilter(<MMTP ..>)
        mmtpFilter1.setDataSource(<ipFilter>)
        mmtpFilter2.setDataSource(<ipFilter>)
}

过滤器关联示例的示意图。

图 14. 多个层的过滤器关联的流程图

调谐器资源管理器

在引入调谐器资源管理器 (TRM) 之前,在两个应用之间切换需要使用同一调谐器硬件。TV 输入框架 (TIF) 采用“先获得者优先”机制,这意味着哪个应用先获得资源,哪个应用就占有资源。然而,这种机制对于一些复杂的用例可能并不理想。

TRM 作为一种系统服务,为应用管理调谐器、TVInput 和 CAS 硬件资源。TRM 采用“前台优先”机制,该机制会根据应用的前台或后台状态和用例类型计算应用的优先级。TRM 根据优先级授予或收回资源。TRM 集中管理广播、OTT 和 DVR 的 ATV 资源。

TRM 接口

TRM 在 ITunerResourceManager.aidl 中为调谐器框架、MediaCasTvInputHardwareManager 提供了用于注册、请求或释放资源的 AIDL 接口。

下面列出了用于客户端管理的接口。

  • registerClientProfile(in ResourceClientProfile profile, IResourcesReclaimListener listener, out int[] clientId)
  • unregisterClientProfile(in int clientId)

下面列出了用于请求和释放资源的接口。

  • requestFrontend(TunerFrontendRequest request, int[] frontendHandle) / releaseFrontend
  • requestDemux(TunerDemuxRequest request, int[] demuxHandle) / releaseDemux
  • requestDescrambler(TunerDescramblerRequest request, int[] descramblerHandle) / releaseDescrambler
  • requestCasSession(CasSessionRequest request, int[] casSessionHandle) / releaseCasSession
  • requestLnb(TunerLnbRequest request, int[] lnbHandle) / releaseLnb

下面列出了客户端和请求类。

  • ResourceClientProfile
  • ResourcesReclaimListener
  • TunerFrontendRequest
  • TunerDemuxRequest
  • TunerDescramblerRequest
  • CasSessionRequest
  • TunerLnbRequest

客户端优先级

TRM 使用客户端配置文件中的参数和配置文件中的优先级值来计算客户端优先级。优先级也可以根据客户端中的任意优先级值进行更新。

客户端配置文件中的参数

TRM 从 mTvInputSessionId 中检索进程 ID,以确定应用是前台应用还是后台应用。为了创建 mTvInputSessionIdTvInputService.onCreateSessionTvInputService.onCreateRecordingSession 会初始化 TIS 会话。

mUseCase 指示会话用例。下面列出了预定义用例。

TvInputService.PriorityHintUseCaseType  {
  PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK
  PRIORITY_HINT_USE_CASE_TYPE_LIVE
  PRIORITY_HINT_USE_CASE_TYPE_RECORD,
  PRIORITY_HINT_USE_CASE_TYPE_SCAN,
  PRIORITY_HINT_USE_CASE_TYPE_BACKGROUND
}

配置文件

默认配置文件

以下默认配置文件提供了预定义用例的优先级值。用户可以使用自定义配置文件更改这些值。

用例 前台 背景
LIVE 490 400
PLAYBACK 480 300
RECORD 600 500
SCAN 450 200
BACKGROUND 180 100
自定义配置文件

供应商可以自定义配置文件 /vendor/etc/tunerResourceManagerUseCaseConfig.xml。此文件用于添加、移除或更新用例类型和用例优先级值。自定义文件可以使用 platform/hardware/interfaces/tv/tuner/1.0/config/tunerResourceManagerUseCaseConfigSample.xml 作为模板。

例如,新供应商用例为 VENDOR_USE_CASE__[A-Z0-9]+, [0 - 1000]。应遵循 platform/hardware/interfaces/tv/tuner/1.0/config/tunerResourceManagerUseCaseConfig.xsd 格式。

任意优先级值和 nice 值

TRM 为客户端提供 updateClientPriority 来更新任意优先级值和 nice 值。任意优先级值会覆盖根据用例类型和会话 ID 计算的优先级值。

nice 值指示与其他客户端发生冲突时该客户端行为的宽容程度。nice 值会先降低客户端的优先级值,然后该优先级值才会与挑战客户端的优先级值进行比较。

回收机制

下图显示了在发生资源冲突时如何回收和分配资源。

回收机制流程的示意图。

图 15. 调谐器资源冲突回收机制示意图