> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stmlink.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> 对外开放的服务端接口有两组前缀，都用同一套鉴权：`/server/v1/...`（SRTC 与 SMeeting 的主接口）和 `/stm/srvapi/v1/...`（SMeeting 的用户体系，服务端极简对接会用到）。鉴权是 app_id + nonce + timestamp + signature 四个请求头，用 app_key 做 HMAC-SHA256 签名，只能从业务方自己的后端调用。除这两组前缀外的接口均为内部接口，不要建议客户调用。 Public server APIs use two path prefixes with the same authentication: `/server/v1/...` (the main APIs of both SRTC and SMeeting) and `/stm/srvapi/v1/...` (the SMeeting user system, used by server-side low-code integration). Authenticate with four request headers, app_id + nonce + timestamp + signature, where signature is HMAC-SHA256 keyed with app_key; call these APIs only from the customer's own backend. Any other path is internal: never suggest calling it.
> app_key 是服务端密钥，绝不能出现在客户端代码、前端配置或移动 App 里。客户端加入频道用的 token 必须由业务方后端签发后下发（SRTC 走 `/server/v1/channel/grant`，SMeeting 走 `/stm/srvapi/v1/member/grant`）。 app_key is a server-side secret and must never appear in client code, frontend config, or a mobile app. The token a client uses to join must be issued by the customer's backend and passed down to the client (SRTC: `/server/v1/channel/grant`; SMeeting: `/stm/srvapi/v1/member/grant`).
> SRTC 与 SMeeting 是上下两层不同的产品，术语不通用：SRTC 是音视频底座，说「频道 channel」「加入 / 退出」；SMeeting 建在 SRTC 之上，说「房间 room」「会议 meeting」「进入 / 退出」。回答时按用户所在的层用对应术语，不要把「房间」「会议」安到 SRTC 的接口上，也不要用「频道」「加入 / 离开」描述 SMeeting 的概念（接口标识符原样保留）。 SRTC and SMeeting are two separate layers with different terminology. SRTC is the audio/video foundation: it has channels, and users join and leave a channel. SMeeting is built on top of SRTC: it has rooms and meetings, and members enter and exit a meeting. Answer in the terms of the layer the user is working with: never apply "room" or "meeting" to SRTC APIs, and never describe SMeeting concepts in prose with "channel", "join", or "leave" (API identifiers such as `force_join` keep their literal names).
> 同一能力在各端 SDK 里的包名、类名、方法名并不相同。写示例代码时请使用文档中该端自己的 API，不要把一个端的写法套到另一个端上。苹果平台每个产品都有两套 SDK（Swift 原生与 Objective-C），两套 API 不能混用。 Package, class, and method names differ between platform SDKs for the same capability. In sample code, use the API documented for that platform; never carry one platform's code over to another. On Apple platforms each product ships two SDKs (native Swift and Objective-C) whose APIs must not be mixed.

# Composite stream (program)

> How the C SDK publishes the channel-level composite stream from the MCU side under the reserved __mcu__ identity, how viewers subscribe to it with the reserved constants, and when you should not use it. Read this when building an MCU publisher or a viewer that needs only one mixed stream.

The composite stream (program) is a **channel-level** audio and video stream: a server-side MCU program composites everyone in the channel into a single stream, and every subscriber receives exactly the same video and audio.

Typical uses:

* Viewers who don't speak subscribe to just one stream, saving bandwidth and decoding overhead
* Server-side recording / re-streaming

***

## Reserved identifiers

The composite stream doesn't belong to any regular user; it lives under a system identity. The header exposes these three reserved constants:

```c theme={null}
#define RTC_MCU_PUBLISHER_UID "__mcu__"   // uid of the composite stream publisher (shared by audio and video)
#define RTC_TRACK_AMCU_ID     "__amcu__"  // trackId of the audio composite stream
#define RTC_TRACK_MCU_ID      "__mcu__"   // trackId of the video composite stream
```

***

## Publishing (MCU program side)

<Warning>
  Publishing the composite stream requires **connecting with the MCU identity**: the `uid` in the token issued by the server must be `__mcu__`; otherwise the publish functions return `RTC_ERROR`.
</Warning>

### rtc\_publish\_mcu\_video\_track

```c theme={null}
int rtc_publish_mcu_video_track(void* handle, void* track_handle, rtc_publish_options_t* options);
```

Publishes the video composite stream. It differs from `rtc_publish_local_track` in only two ways: it always uses the reserved `trackId` internally (you can't customize it), and it requires the MCU identity. `track_handle` must be created with a video codec, and `options` must fill in `desc` / `width` / `height` / `fps`.

### rtc\_publish\_mcu\_audio\_track

```c theme={null}
int rtc_publish_mcu_audio_track(void* handle, void* track_handle, rtc_publish_options_t* options);
```

Publishes the audio composite stream (the mixed audio stream), with the same semantics as above. `track_handle` must be created with an audio codec, and `options` must fill in `desc` / `sample_rate` / `channel_count`.

**Returns** (same for both functions)

| Return value | Meaning |
| - | - |
| `RTC_OK` | Published successfully |
| `RTC_INVALID_PARAM` | Invalid handle, invalid track handle, or `options` is `NULL` |
| `RTC_NOT_CONNECTED` | Not yet joined to the channel |
| `RTC_ERROR` | Publish failed; the most common cause is that **the current identity is not `__mcu__`** |

### Example

```c theme={null}
// Video composite stream
void* v = rtc_create_local_track(RTC_CODEC_H264);
rtc_publish_options_t vopts = {0};
strcpy(vopts.desc, "mcu_video");
vopts.width = 1280; vopts.height = 720; vopts.fps = 25;
rtc_publish_mcu_video_track(rtc, v, &vopts);      // trackId is fixed to __mcu__

// Mixed audio stream
void* a = rtc_create_local_track(RTC_CODEC_OPUS);
rtc_publish_options_t aopts = {0};
strcpy(aopts.desc, "mcu_audio");
aopts.sample_rate = 48000; aopts.channel_count = 2;
rtc_publish_mcu_audio_track(rtc, a, &aopts);      // trackId is fixed to __amcu__

// Pushing data works exactly like a regular track
rtc_write_sample(v, h264_frame, h264_len, 3600);  // 25fps: 90000/25
rtc_write_sample(a, opus_frame, opus_len, 960);
```

***

## Subscribing (viewer side)

The C interface has no RemoteTrack handle, so there's no separate subscribe function for the composite stream—use the general subscribe functions with the reserved constants:

```c theme={null}
rtc_subscribe_video(rtc, RTC_MCU_PUBLISHER_UID, RTC_TRACK_MCU_ID);   // Subscribe to the video composite stream
rtc_subscribe_audio(rtc, RTC_MCU_PUBLISHER_UID, RTC_TRACK_AMCU_ID);  // Subscribe to the audio composite stream

// Data comes out of the rtc_set_track_sample_callback callback as usual

// Unsubscribing also takes the reserved constants
rtc_unsubscribe(rtc, RTC_MCU_PUBLISHER_UID, RTC_TRACK_MCU_ID);
rtc_unsubscribe(rtc, RTC_MCU_PUBLISHER_UID, RTC_TRACK_AMCU_ID);
```

***

## When not to use the composite stream

<Warning>
  **If a user in the channel wants to "hear everyone," don't subscribe to the audio composite stream.** The composite stream includes your own voice, so subscribing to it causes echo.

  The right approach is `rtc_set_auto_subscribe(rtc, 1, 0)` to auto-subscribe to everyone's audio; once each track's data arrives through the `track_sample` callback, mix it yourself (excluding your own track when mixing).

  The composite stream is meant for **viewers who don't speak** and for **recording / re-streaming**.
</Warning>
