[RTL8735B / AmebaPro2] rtsp_transport_init() writes past the end of transport[]

Date: 2026-08-28 SDK: AmebaPro2 (Arduino 4.1.0 / ameba-rtos-pro2) File: component/network/rtsp/rtsp_api.c

  1. Summary

rtsp_transport_init() advances its pointer with += i instead of ++, so it visits array indices 0, 1, 3, 6, 10 … rather than 0, 1, 2, 3 …

With the shipped RTSP_MAX_STREAM_NUM of 2 the loop happens to visit exactly [0] and [1] and the defect is invisible. Any project that raises RTSP_MAX_STREAM_NUM gets an out-of-bounds write on the third iteration and an uninitialised transport[2].

We raised it to 3 to add an metadata track, which is how we met this.


2. The code

c

void rtsp_transport_init(struct rtsp_context *rtsp_ctx)
{
    struct rtsp_transport *transport = &rtsp_ctx->transport[0];
    for (int i = 0; i < RTSP_MAX_STREAM_NUM; i++) {
        transport += i;                 /* <-- accumulates */
        transport->serverport_low  = rtsp_ctx->id * 2 + RTP_SERVER_PORT_BASE;
        transport->serverport_high = rtsp_ctx->id * 2 + RTP_SERVER_PORT_BASE + 1;
        transport->port_low        = rtsp_ctx->id * 2 + RTP_PORT_BASE;
        transport->port_high       = rtsp_ctx->id * 2 + RTP_PORT_BASE + 1;
        transport->clientport_low  = rtsp_ctx->id * 2 + RTP_CLIENT_PORT_BASE;
        transport->clientport_high = rtsp_ctx->id * 2 + RTP_CLIENT_PORT_BASE + 1;
        transport->isRtp    = 1;
        transport->isTcp    = 0;
        transport->castMode = UNICAST_UDP_MODE;
        transport->ttl      = 0;
    }
}

Pointer positions per iteration:

i = 0   transport += 0   ->  [0]     initialised
i = 1   transport += 1   ->  [1]     initialised
i = 2   transport += 2   ->  [3]     out of bounds; [2] never initialised
i = 3   transport += 3   ->  [6]

3. What the out-of-bounds write lands on

rtsp_api.h:

c

struct rtsp_context {
    ...
    struct rtsp_transport transport[RTSP_MAX_STREAM_NUM];
    struct rtsp_session   session;          /* <-- immediately after */
    u16 rtpseq[RTSP_MAX_STREAM_NUM];
    ...
};

With RTSP_MAX_STREAM_NUM = 3, the third iteration writes ten fields into the memory occupied by struct rtsp_session.


4. How it surfaced

We added an ONVIF metadata track as a third stream. Every device-side probe reported success — the stream was configured, the RTP handler ran, sendto() returned without error — and the ONVIF Device Test Tool counted zero metadata frames.

The two SETUP responses side by side named the cause:

video track       Transport: RTP/AVP/UDP;unicast;client_port=52578-52579;server_port=55608-55609
metadata track    Transport: RTP/AVP/UDP;unicast;client_port=53274-53275;server_port=0-0

transport[2] was still zero-filled, so the device advertised that it would send from port 0. The client was therefore never listening where the packets actually came from.


5. Suggested fix

c

void rtsp_transport_init(struct rtsp_context *rtsp_ctx)
{
    struct rtsp_transport *transport;
    for (int i = 0; i < RTSP_MAX_STREAM_NUM; i++) {
        transport = &rtsp_ctx->transport[i];
        ...
    }
}

Indexing rather than incrementing also means the loop cannot drift if RTSP_MAX_STREAM_NUM changes again.


6. Why this may be worth fixing upstream even at MAX = 2

  • ONVIF Profile T §7.13 makes metadata streaming mandatory, and metadata needs a third stream alongside video and audio. Any customer pursuing Profile T certification on this SDK will raise RTSP_MAX_STREAM_NUM and hit this.
  • The failure mode is quiet. There is no crash, no error return and no log line; only a server_port of 0 in a SETUP response, and a stream that transports nothing.
  • The out-of-bounds write is into a live structure in the same object, so its effects would depend on the layout of struct rtsp_session rather than faulting.

7. Related observation (lower priority, separate item)

sdp_fill_m_field() is called with the payload type computed as

c

(pt >= RTP_PT_DYN_BASE) ? (pt + stream_id) : pt

while create_sdp_a_string() writes a bare codec->pt into a=rtpmap, and each codec’s RTP handler puts codec->pt on the wire.

These agree today only because the single dynamic payload type in use (H.264, 96) always sits at stream_id 0, so 96 + 0 == 96. A dynamic payload type on any other stream index produces an m= line, an a=rtpmap line and an RTP header that disagree.

We hit this three times while adding the metadata track and corrected each of them locally. It may be worth making the three sites share one expression.

:waving_hand: Thanks for your post!

For documentation, SDK resources, FAQs, and community guidelines, please visit: here

Happy building with Ameba!


:waving_hand: 感谢您的发帖!

如需查阅官方文档、SDK 资源、常见问题(FAQ)及社区使用指南,请参考: 這裏

祝您使用 Ameba 开发愉快!

Hi @Hsu_Shawn,

Regarding this question, I would like to clarify the following:

Where does the metadata track live?

  • Is it a 3rd stream on the same server as your main stream (e.g., port 554, which already carries H264 + JPEG snapshot)—i.e., all tracks under one rtsp://ip:554
  • Or is it on a separate server/port (e.g., 557)?

This helps us understand the expected behavior, since the SDK’s RTSP core currently supports up to 2 streams per server.

Hello Pammy,

Thank you for the clarification. Let me first lay out our overall architecture, then answer where the metadata track lives.

Hardware encode channels (4)



CH

|

Resolution / Codec

|

Purpose

|

  • | - | - |


    CH0

    |

    1080p H.264/H.265

    |

    Main stream → RTSP 554

    |


    CH1

    |

    720p H.264

    |

    Web live preview + RTSP 555

    |


    CH2

    |

    720p MJPEG

    |

    JPEG profile → RTSP (third server)

    |


    CH3

    |

    VGA RGB

    |

    Motion detection only — not exposed via RTSP

    |

RTSP server / track mapping



Port

|

Tracks

|

  • | - |


    554 (profile_1)

    |

    CH0 video + G.711 audio + metadata (m=application)

    |


    555 (profile_2)

    |

    CH1 video + G.711 audio

    |


    (third server)

    |

    CH2 MJPEG video + G.711 audio

    |

Answer to your question: the metadata track lives under the main server (port 554), alongside CH0’s H.264/H.265 video and the G.711 audio track — all under a single rtsp://ip:554. It is not on a separate server/port.

Regarding the 2-stream-per-server limit: we understand the SDK’s RTSP core supports up to 2 streams per server (RTSP_MAX_STREAM_NUM = 2), and on 554 the video and audio tracks already occupy both slots.

The metadata track is therefore not carried through an RTSP core stream slot. We implemented it as an application-layer side channel: it is bound to port 554 and periodically injects an ONVIF metadata XML document (per ONVIF Streaming Spec §5.1.2.1.1, roughly once per second) as an m=application track. Concretely, our layer exposes rtsp_meta_set_port(554) to bind the port and rtsp_meta_push(xml, len) to enqueue each document; the push is non-blocking (it returns -EAGAIN when no client is subscribed rather than stalling the pipeline). Because this path does not consume either of the core’s two stream slots, the m=application track coexists with video and audio on the same server without exceeding the limit.

The metadata track appears only on profile_1 (554); profile_2 (555) has no metadata configuration, so its DESCRIBE does not advertise m=application. This metadata streaming is implemented and working, carrying Property Events (motion / tamper, etc.).

Two questions for you, to make sure our approach is sound long-term:

  1. Does this side-channel approach risk any conflict with the SDK’s own RTP/RTSP scheduling or session teardown that we should be aware of — particularly under concurrent sessions or during client SETUP/TEARDOWN?
  2. Is there an officially recommended way to carry a metadata track on this platform — for example, whether a future SDK release will raise RTSP_MAX_STREAM_NUM, or provide native m=application support — so we can plan for long-term maintainability rather than relying on our own side channel?

Thank you.

Best regards,

Shawn

Hi @Hsu_Shawn,

  1. No conflict is expected with the SDK’s RTSP stream manager itself, including during client SETUP/TEARDOWN. The main thing to validate is whether your separate metadata socket can safely share port 554 with the SDK RTSP server, especially when multiple clients connect and disconnect.

  2. At present, we are not aware of any plan to increase the two-stream-per-server limitation. The RTSP source code is open source, so developers may extend the RTSP implementation according to their application requirements at the moment.

    Recommend using the platform’s built-in SEI support instead of a custom channel. The video driver exposes video_sei_write() with a user-data buffer, and the MMF2 video framework calls it automatically for every encoded frame (see mmf2_video_example_av2_init.c for a working example). Your ONVIF event (motion/tamper) XML simply gets passed in as user data, and the SDK handles the rest. Hope it helps!

Hello Pammy,
Thank you for your response, which helped us clarify several issues. We still have a few more questions we would like to ask you.
Technical Questions — AMB82-mini (RTL8735B) SDK

Technical Questions — AMB82-mini (RTL8735B) SDK

Platform: AMB82-mini / RTL8735B, Arduino AmebaPro2 core 4.1.0, DDR2 128MB.

Two SDK capability questions. (We patch the SDK source ourselves and rebuild the libraries, so we mainly need to know what the chip/stack can do and the recommended approach.)


1. IPv6 — cannot add a static IPv6 address (highest priority)

We need to set a static (manual) IPv6 address, but it fails.

LWIP_IPV6_NUM_ADDRESSES = 3, and all three slots are already used after SLAAC auto-configuration (1 link-local + 2 from Router Advertisement), so netif_add_ip6_address() for our static address returns failure — no free slot.

We have already patched our own SDK source (enabled LWIP_IPV6, extended the RTP/RTSP/SDP paths for IPv6 multicast, etc.) and rebuilt those libraries. The remaining blocker is the address-count constant.

Questions:

  1. LWIP_IPV6_NUM_ADDRESSES is a compile-time lwIP constant that appears baked into prebuilt libraries we do NOT have source for (libwlan.a, libeap.a). Can Realtek provide these libraries rebuilt with a larger LWIP_IPV6_NUM_ADDRESSES (e.g. 5 or 6)?
  2. Is there a runtime API to remove/replace one of the SLAAC (Router-Advertisement) IPv6 addresses to free a slot for a static one?
  3. Is there any recommended way to coexist a static IPv6 address with SLAAC on this stack?

2. RTSP/RTP — adding a custom RTP header extension + custom PLAY headers

We stream a recorded MP4 from SD over RTSP using the Demuxer class (Demuxer → StreamIO → RTSP). A normal RTSP client plays it fine. But a stricter client we must support requires two things the SDK’s RTSP/RTP path does not currently do:

(a) Custom RTP header extension per packet. Each outgoing RTP packet must carry a small custom header extension (an absolute NTP timestamp + a few status bits). The SDK sends plain RTP with no extension.

(b) Custom RTSP request/response headers. The client’s PLAY includes custom headers (Require: and Range: clock=...) and expects the response to echo Range and include RTP-Info.

We already patch rtsp_api.c / rtp_api.c for other features, so patching is acceptable — we mainly want to avoid fighting the SDK’s internal RTP scheduling.

Questions:

  1. rtp_api.c’s rtp_fill_header() already takes an extension parameter and sets the RTP x bit. What is the recommended way to attach the actual extension PAYLOAD (profile ID + data words) to each outgoing RTP packet — is there an existing buffer/API for the extension body, or do we extend rtp_fill_header / the send path ourselves?
  2. Any internal API you’d recommend for setting a per-frame RTP extension without disrupting the SDK’s packetisation/timestamping?
  3. For the RTSP layer — is parsing extra request headers (Require:, Range:) and adding extra response headers (Range, RTP-Info) something the SDK server supports, or do we handle it in rtsp_api.c?

We can share source/details on any of these.

Hello Pammy,
MP4Recording — recording a 3rd track (metadata/user-data) into the MP4 file

MP4Recording::setRecordingDataType() only accepts type <= STORAGE_AUDIO (STORAGE_VIDEO / STORAGE_AUDIO / STORAGE_ALL). We need to record a third track into the MP4 file — a timed metadata / user-data track (our own binary/text payload per frame, alongside video+audio), similar to an MP4 meta/text track.

  1. Does the underlying MP4 muxer support writing a 3rd (metadata/user-data/timed-text) track into the MP4 file, even though the MP4Recording wrapper’s setRecordingDataType() caps at STORAGE_AUDIO?
  2. If the muxer supports it, is there a lower-level API (below MP4Recording) to add such a track, or a way to extend record_type?
  3. If not — is there any supported way to get application data into the recorded MP4 on this platform?