- 音视频
- 音频处理
- 嵌入式
- 物联网
- 语音
【免费下载链接】esp-adf
Espressif Advanced Development Framework for Multimedia Applications
esp_media_service是 ESP-ADF(Espressif Advanced Development Framework for Multimedia Applications)中为音频和视频服务提供的通用媒体接口层。它位于esp_service服务框架之上,为应用提供了一套统一的"创建服务 → 配置媒体 stream → 链接源与接收端 → 启动传输"的媒体流模型:应用只需把源服务(Source)的 stream 链接到接收端服务(Sink)的 stream,服务完成链接并启动后,媒体帧就会自动通过 provider 与 track 接口流动,应用无需手动转发媒体帧。阅读本文后,你将掌握该组件的角色模型、链接流程、track manager 两种缓存模式、源/接收端服务实现范式,以及安全的停止与中止规则,并可直接基于仓库源码与单元测试复现验证。
组件定位与功能特性
esp_media_service是构建于esp_service之上的媒体层。一个服务对象就是一个实例,派生服务需要把esp_media_service_t作为第一个成员内嵌(对应 esp_media_service.h 中的结构定义)。组件提供的能力包括:
- 统一的音视频服务模型(音频、视频、封装器服务共用一套接口);
- 支持源(SRC)、接收端(SINK)以及源接收一体(SRC_SINK)三种服务角色;
- 基于 stream ID(
esp_media_stream_id_t)的媒体端点寻址; - 链接(link)时接收端与源之间的请求协商(request negotiation);
- Provider 读取接口与 track manager 写入接口的读写分离设计;
- 提供默认的内存 track manager,用于排队缓存媒体帧;
- 通过
esp_service统一管理服务生命周期(start/stop)。
从依赖关系看,idf_component.yml 声明该组件公开依赖espressif/esp_service(^0.5)与espressif/gmf_core(^1.0,内部帧队列基于esp_gmf_data_queue实现),组件版本为 0.5.0。编译入口见 CMakeLists.txt,共包含 4 个源文件:esp_media_service.c(服务模型与链接)、esp_media_track_mngr.c(track 队列管理)、esp_media_track.c(写入侧 API)、esp_media_provider.c(读取侧 API 分发)。
核心概念一:服务角色(Service Roles)
媒体服务可以是以下三种角色之一(定义于 esp_media_service.h):
ESP_MEDIA_ROLE_SRC:产生媒体帧(源)。ESP_MEDIA_ROLE_SINK:消费媒体帧(接收端)。ESP_MEDIA_ROLE_SRC_SINK:既消费媒体帧,也产生媒体帧(例如转码、混流服务),其取值是ESP_MEDIA_ROLE_SRC | ESP_MEDIA_ROLE_SINK。
服务可以通过实现get_role()媒体操作,让esp_media_service_link()在链接时校验所选源和接收端是否兼容。从 esp_media_service.c 的链接实现可以看到,链接会分别检查源服务的角色是否含ESP_MEDIA_ROLE_SRC、接收端服务的角色是否含ESP_MEDIA_ROLE_SINK,不满足则返回ESP_ERR_NOT_SUPPORTED并终止链接。
核心概念二:Stream 与媒体端点
服务之间通过 stream ID 通信。stream ID 本质上是一个实例内可寻址的"媒体流端点"序号:
typedef uint16_t esp_media_stream_id_t; #define ESP_MEDIA_DEFAULT_STREAM ((esp_media_stream_id_t)0)一个 stream 包含一组 track(轨道),每个 track 携带独立的媒体元数据与队列。使用ESP_MEDIA_DEFAULT_STREAM表示第一个 stream:
esp_media_stream_id_t stream = ESP_MEDIA_DEFAULT_STREAM; esp_media_service_link(src_service, stream, sink_service, stream);一个服务可以拥有多个 stream(例如一路音频采集 + 一路视频采集),测试代码mock_media_service_t中即为每个服务预留了MAX_STREAMS(2 个)stream 槽位(见 ut_esp_media_service.c)。
核心概念三:媒体帧与 Track 元数据
媒体帧描述符esp_media_frame_t(定义于 esp_media_service_types.h)包含以下字段:
| 字段 | 说明 |
|---|---|
track_id | 拥有该帧的 track ID |
type | 帧的媒体类型(音频/视频/封装器) |
data/size | 帧载荷指针与字节数 |
pts/dts | 显示时间戳/解码时间戳(单位 us,dts 与 pts 不分离时相同) |
flags | 帧标志位(见下) |
user_data | 可选的属主自定义元数据 |
Track 类型枚举esp_media_track_type_t包括UNKNOWN、AUDIO、VIDEO与MUXER四种;Track 元数据esp_media_track_info_t内含服务本地id、type及联合体形式的音频信息(codec FourCC、采样率、位深、声道数、码率)或视频信息(codec、宽高、帧率、码率)。
帧标志位(ESP_MEDIA_FRAME_FLAG_XXX)用于表达控制语义:
ESP_MEDIA_FRAME_FLAG_TRACK_CHANGED:零尺寸控制帧,携带更新后的 track 信息;ESP_MEDIA_FRAME_FLAG_KEY:关键帧;ESP_MEDIA_FRAME_FLAG_EOS:流结束;ESP_MEDIA_FRAME_FLAG_TRACK_REMOVED:零尺寸控制帧,指示 track 被移除。
典型用法与生命周期
将源服务链接到接收端服务,然后启动两个服务:
esp_media_stream_id_t stream = ESP_MEDIA_DEFAULT_STREAM; esp_media_service_link(src_service, stream, sink_service, stream); esp_service_start(sink_service); esp_service_start(src_service);服务链接并启动后,媒体数据会通过源服务提供的 provider 从源服务流向接收端服务。典型的完整生命周期如下:
Provider 与 Track 接口:读写分离的媒体数据通路
媒体数据使用两个方向的接口:
esp_media_provider_t(读取侧):查询 track 数量与元数据、注册事件回调、获取或读取帧、释放帧、中止阻塞读取。声明见 esp_media_provider.h。esp_media_track_mngr_t(写入侧):管理 track 队列,写入 API 定义在 esp_media_track.h。
接收端在链接时从上游源服务获取esp_media_provider_t;源服务通过esp_media_track_write_frame()写入帧。provider 句柄本身只是一个"ops 函数表 + 上下文指针"的轻量结构(esp_media_provider_ops_t+ctx),因此链接时传递的是句柄拷贝,不涉及帧数据拷贝。
Provider 读取侧共 7 个操作(见 esp_media_service_types.h):get_track_num、get_track_info、set_event_cb、acquire_frame、read_frame、release_frame、abort。其中:
acquire_frame以"零拷贝"方式取帧:返回的帧直接指向 provider 内部存储,调用方处理完必须用release_frame归还;read_frame则把帧拷贝进调用方提供的缓冲区(out_frame->data/size),读取完成后内部自动释放,缓冲区不足时返回ESP_ERR_INVALID_SIZE;acquire_frame的超时语义为:0表示不等待、UINT32_MAX表示永久等待,其余为毫秒数。
必须遵守的约定:通过esp_media_provider_acquire_frame()获取的帧必须始终使用esp_media_provider_release_frame()释放;帧释放后,不要再访问frame.data(其指针在内部可能已被复用或归还)。
Track Manager:默认内存帧存储详解
esp_media_track_mngr_t是默认的内存帧存储(默认 provider 实现)。它导出一个 provider 句柄,并为单个或多个 track 管理队列。其配置结构(esp_media_track_mngr.h)为:
typedef struct { uint16_t max_track_num; /*!< 最大 track 数量 */ bool use_global_cache; /*!< 是否使用所有 track 共用的按到达顺序排列的 cache */ struct { size_t cache_size; /*!< 共享队列字节数,0 使用默认值 */ } global_cache; } esp_media_track_mngr_cfg_t;从源码 esp_media_track_mngr.c 可以确认默认参数:单 track 队列默认深度相关常量为MEDIA_TRACK_DEFAULT_QUEUE_NUM = 4,默认队列字节容量为MEDIA_TRACK_DEFAULT_QUEUE_SIZE = 8 * 1024(8 KB);global cache 未指定大小时同样默认 8 KB。
两种 payload 所有权模式
每个 track 通过cache_cfg.cache_type选择缓存模式(esp_media_track_cache_type_t):
ESP_MEDIA_TRACK_CACHE_INTERNAL(内部缓存):track manager 复制并持有帧 payload 数据。此模式下支持esp_media_track_acquire_frame()先获取可写存储、填充数据后调用esp_media_track_write_frame()提交(或esp_media_track_release_frame()丢弃),可实现零拷贝写入;配套的track_cache配置还支持cache_size(队列字节容量)、addr_align(帧数据地址对齐,0 表示按指针大小对齐)、size_align(帧尺寸对齐)。ESP_MEDIA_TRACK_CACHE_USER(用户缓存):track manager 只缓存帧元数据(track_frame_node_t),payload 由用户持有,帧被消费后通过frame_release回调归还给属主。此模式不注册frame_release回调即无效;user_queue.queue_num为元数据队列深度(0 表示使用默认值 4)。
两种模式的行为差异已由单元测试覆盖:测试用例 "user cache rejects acquire_frame and supports write plus sync read" 验证了 USER 模式下调esp_media_track_acquire_frame()会返回ESP_ERR_NOT_SUPPORTED,且每读走一帧都会触发一次frame_release回调(见 ut_esp_media_service.c)。
Global Cache:音视频交错传输场景
track manager 还支持global cache模式:所有 track 共用一个按到达顺序排列的队列,帧的先后顺序完全由写入顺序决定,读取时按到达顺序返回(select_read_track在 global cache 下固定返回队列头部,见 esp_media_track_mngr.c)。这适用于 RTMP 等需要音视频帧交错混流的传输场景。
务必注意:必须在添加 track 之前启用 global cache。可以通过创建配置中的cfg.use_global_cache = true启用,也可以在创建后用esp_media_track_mngr_set_global_cache(mngr, true, cache_size)动态启用——该 API 在已有 track 被添加后会返回ESP_ERR_INVALID_STATE(源码校验见 esp_media_track_mngr.c)。测试场景中track_queue_size()等相关逻辑同样印证了 global cache 与 per-track 队列二选一的关系。
实现源服务(Source)
源服务通常持有一个esp_media_track_mngr_t:
esp_media_track_mngr_cfg_t cfg = { .max_track_num = 2, }; esp_media_track_mngr_create(&cfg, &svc->mngr); esp_media_track_mngr_add_track(svc->mngr, &audio_track); esp_media_track_mngr_add_track(svc->mngr, &video_track); esp_media_track_mngr_get_provider(svc->mngr, &svc->provider);源服务实现媒体操作中的get_provider(),把上述 provider 句柄暴露给下游;随后在产生帧的任务中通过esp_media_track_write_frame()写入帧。注意写入的帧必须携带与已配置 track 匹配的track_id与type(find_track_by_frame按这两个字段定位 track,见 media_track_mngr.h)。
根据 agent.md 的指引,源服务有两种实现模式:
- 自定义 provider ops:当源已经拥有自己的帧管线(硬件缓冲区、编码器输出队列、自定义 track 规则等)时,直接实现
esp_media_provider_ops_t并在get_provider()中返回该句柄(out_provider->ops = &s_my_provider_ops; out_provider->ctx = svc;),无需引入 track manager。 - track manager(推荐默认):当源需要一个外部帧队列来解耦生产者/消费者任务、多 track 缓存或 RTMP 式交错投递时,使用上述 track manager 范式。
动态 track 变更场景下,源可以调用esp_media_track_mngr_update_track()(变更元数据)与esp_media_track_mngr_remove_track()(移除 track)。这两个 API 会在队列中写入零尺寸控制帧(分别带ESP_MEDIA_FRAME_FLAG_TRACK_CHANGED/ESP_MEDIA_FRAME_FLAG_TRACK_REMOVED标志),当接收端 acquire/read 到该控制帧时,provider 事件回调被同步触发(见 esp_media_track_mngr.c 及 ut_esp_media_service.c 的exercise_track_update验证)。
实现接收端服务(Sink)
接收端实现媒体操作中的set_provider()并保存上游 provider。传入NULL表示解除链接,此时应清空本地保存的 provider 状态:
static esp_err_t my_sink_set_provider(esp_service_t *service, esp_media_stream_id_t stream, const esp_media_provider_t *provider) { my_sink_t *sink = (my_sink_t *)service; if (provider == NULL) { sink->provider.ops = NULL; sink->provider.ctx = NULL; return ESP_OK; } sink->provider = *provider; return esp_media_provider_set_event_cb(&sink->provider, my_event_cb, sink); }接收端任务读取帧,并释放每一个已获取的帧:
while (!sink->stop) { esp_media_frame_t frame = {0}; if (esp_media_provider_acquire_frame(&sink->provider, &frame, timeout_ms) == ESP_OK) { process_frame(&frame); esp_media_provider_release_frame(&sink->provider, &frame); } }接收端还应实现get_request()以声明链接期需求(目前唯一的请求字段是need_global_cache,用于请求源侧开启 global cache 以满足音视频交错消费),并在set_provider()中注册事件回调(如TRACK_UPDATED、TRACK_REMOVED、TRACKS_ABORT),更换 provider 前应清除旧回调。
链接流程源码解析:esp_media_service_link 的六步协商
esp_media_service_link(src_service, src_stream, sink_service, sink_stream)的实际执行顺序可以从 esp_media_service.c 逐行确认:
- 若源实现了
get_role(),校验其角色包含ESP_MEDIA_ROLE_SRC; - 若接收端实现了
get_role(),校验其角色包含ESP_MEDIA_ROLE_SINK; - 调用接收端的
get_request()获取链接期请求; - 若请求获取成功,则调用源的
set_request()应用该请求(源未实现或返回ESP_ERR_NOT_SUPPORTED属正常情况,其余错误中止链接); - 调用源的
get_provider()取得 provider; - 调用接收端的
set_provider(provider)完成接入。
与之对称,esp_media_service_unlink()在完成同样的角色校验后,调用sink.set_provider(NULL)清空接收端的上游 provider(见 esp_media_service.c)。测试用例 "media service unlink clears sink reader after start stop" 验证了 unlink 后接收端linked_provider.ops被清空、且可以再次 link 并继续传输(见 ut_esp_media_service.c)。
停止与中止:降低停止顺序脆弱性的设计
媒体接口的设计目标是让停止顺序不再脆弱。核心规则如下:
- 源服务停止时应调用
esp_media_track_write_abort()。该 API 会把 manager 置为 aborted 状态,通过ESP_MEDIA_PROVIDER_EVENT_TRACKS_ABORT事件通知下游 provider,并唤醒所有阻塞在队列读/写上的任务(实现见 esp_media_track.c)。 - 接收端停止时应设置本地停止标志,调用
esp_media_provider_abort(),等待任务退出,释放已获取的帧,并取消链接(unlink)。 - 如果 track manager 通过链接被共享,先取消链接,再 reset 或 destroy。
- 当其他任务仍可能持有已获取帧或阻塞在队列上时,不要 reset 或 destroy track manager。
中止后的恢复能力由esp_media_track_clear_abort()提供:清除 abort 状态并重置队列(保留 track 配置),适用于"停止后重放"等场景;esp_media_track_mngr_reset()则更进一步移除所有 track 并清空队列(global cache 模式保留),调用后必须重新 add track。
这一套停止/中止语义有专门的并发单元测试支撑:测试 "write abort wakes reader then clear abort resumes flow" 与 "reader abort wakes writer then clear abort resumes flow" 通过两个任务(agent/peer)在写侧或读侧阻塞时触发 abort,断言被阻塞方以ESP_ERR_INVALID_STATE被唤醒、TRACKS_ABORT事件被触发,且clear_abort后读写可以恢复(见 ut_esp_media_service.c 与run_abort_scenario实现)。测试入口见 test_apps/main/app_main.c,按[esp_media_service]标签运行全部单元测试。
常见范式速查
- 源侧三件套:
esp_media_track_mngr_create()→esp_media_track_mngr_add_track()→esp_media_track_mngr_get_provider(),get_provider()操作中返回该 provider 句柄。 - 接收端读取循环:
acquire_frame→process_frame→release_frame,直到停止标志置位。 - RTMP 式交错传输请求:接收端
get_request()中置request->need_global_cache = true;源set_request()中调用esp_media_track_mngr_set_global_cache(mngr, true, cache_size)(必须在 add track 之前)。
实现或评审相关服务时,可对照 agent.md 中的评审检查清单逐项确认:角色操作与链接方式匹配、set_request()先于get_provider()应用、global cache 在添加 track 前启用、帧的track_id/type与配置 track 匹配、acquire 的帧始终 release、USER 缓存 track 配置了有效frame_release、接收端处理TRACKS_ABORT、链接状态下先 unlink 再 reset/destroy、set_provider(NULL)清理回调与本地 provider 状态。
- 音视频
- 音频处理
- 嵌入式
- 物联网
- 语音
【免费下载链接】esp-adf
Espressif Advanced Development Framework for Multimedia Applications
相关推荐
ESP-ADF 的 esp_media_service 组件:统一音视频服务媒体接口实战指南
ESP ADF 的 esp_media_service 组件:统一音视频服务媒体接口实战指南 esp_media_service 是 ESP ADF(Espre
音视频音频处理嵌入式物联网语音ESP-ADF 多媒体示例集:8 个音频与视频示例工程的完整实战指南
ESP ADF 多媒体示例集:8 个音频与视频示例工程的完整实战指南 ESP ADF(Espressif Advanced Development Framew
音视频音频处理嵌入式物联网语音TradingAgents-CN:AI金融投资分析框架的完整使用指南
TradingAgents CN:AI金融投资分析框架的完整使用指南 在信息爆炸的金融市场中,如何从海量数据中提取有价值的投资信号?如何避免情绪化决策,建立科学
人工智能大模型AI Agent多智能体金融科技后端前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考