### Media Stream Track Configuration Examples Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/09-configuration.md Practical examples for configuring specific video and audio tracks. ```c EasyRTC_MediaStreamTrack videoTrack = {0}; videoTrack.codec = EasyRTC_CODEC_H264; strcpy(videoTrack.trackId, "0"); // Primary video track strcpy(videoTrack.streamId, "stream0"); videoTrack.kind = EasyRTC_MEDIA_STREAM_TRACK_KIND_VIDEO; ``` ```c EasyRTC_MediaStreamTrack audioTrack = {0}; audioTrack.codec = EasyRTC_CODEC_OPUS; strcpy(audioTrack.trackId, "1"); // Audio track (distinct from video) strcpy(audioTrack.streamId, "stream0"); audioTrack.kind = EasyRTC_MEDIA_STREAM_TRACK_KIND_AUDIO; ``` ```c EasyRTC_MediaStreamTrack videoTrack = {0}; videoTrack.codec = EasyRTC_CODEC_VP8; strcpy(videoTrack.trackId, "main"); strcpy(videoTrack.streamId, "surveillance"); videoTrack.kind = EasyRTC_MEDIA_STREAM_TRACK_KIND_VIDEO; ``` -------------------------------- ### Implement Cascade Release on Setup Failure Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/08-errors.md Uses a goto label to ensure all allocated resources are properly freed in reverse order if any step in the connection setup fails. ```c EasyRTC_PeerConnection pc = NULL; EasyRTC_Transceiver video_tx = NULL; EasyRTC_Transceiver audio_tx = NULL; EasyRTC_DataChannel dc = NULL; // Create connection int ret = EasyRTC_CreatePeerConnection(&pc, &config, state_cb, ctx); if (ret != EASYRTC_ERROR_NO) { goto error; } // Add video ret = EasyRTC_AddTransceiver(&video_tx, pc, &video_track, &init, tx_cb, ctx); if (ret != EASYRTC_ERROR_NO) { goto error; } // Add audio ret = EasyRTC_AddTransceiver(&audio_tx, pc, &audio_track, &init, tx_cb, ctx); if (ret != EASYRTC_ERROR_NO) { goto error; } // Add datachannel ret = EasyRTC_AddDataChannel(&dc, pc, "control", dc_cb, ctx); if (ret != EASYRTC_ERROR_NO) { goto error; } return 0; error: fprintf(stderr, "Setup failed: %d; cleaning up\n", ret); if (dc != NULL) EasyRTC_FreeDataChannel(&dc); if (audio_tx != NULL) EasyRTC_FreeTransceiver(&audio_tx); if (video_tx != NULL) EasyRTC_FreeTransceiver(&video_tx); if (pc != NULL) EasyRTC_ReleasePeerConnection(&pc); return 1; ``` -------------------------------- ### Configure Viewer/Monitor Scenario Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/09-configuration.md Setup receive-only media streams for monitoring. ```c EasyRTC_Configuration config = {0}; config.iceTransportPolicy = EasyRTC_ICE_TRANSPORT_POLICY_ALL; strcpy(config.iceServers[0].urls, "stun.l.google.com:19302"); // Video: receive only EasyRTC_MediaStreamTrack videoTrack = {0}; videoTrack.codec = EasyRTC_CODEC_H264; videoTrack.kind = EasyRTC_MEDIA_STREAM_TRACK_KIND_VIDEO; EasyRTC_RtpTransceiverInit videoInit = {0}; videoInit.direction = EasyRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY; // Audio: receive only EasyRTC_MediaStreamTrack audioTrack = {0}; audioTrack.codec = EasyRTC_CODEC_OPUS; audioTrack.kind = EasyRTC_MEDIA_STREAM_TRACK_KIND_AUDIO; EasyRTC_RtpTransceiverInit audioInit = {0}; audioInit.direction = EasyRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY; ``` -------------------------------- ### Define and Initialize EasyRTC_IceServer Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/07-types.md Structure for STUN/TURN server configuration with examples for both server types. ```c typedef struct { char urls[MAX_ICE_CONFIG_URI_LEN + 1]; char username[MAX_ICE_CONFIG_USER_NAME_LEN + 1]; char credential[MAX_ICE_CONFIG_CREDENTIAL_LEN + 1]; } EasyRTC_IceServer; ``` ```c EasyRTC_IceServer stun = {0}; strcpy(stun.urls, "stun.l.google.com:19302"); EasyRTC_IceServer turn = {0}; strcpy(turn.urls, "turn.example.com:3478?transport=udp"); strcpy(turn.username, "user"); strcpy(turn.credential, "pass"); ``` -------------------------------- ### Implement EasyRTC Activity Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/11-jni-android.md A complete example showing JNI callback implementation and peer connection setup in a Kotlin Activity. ```kotlin class EasyRTCActivity : AppCompatActivity() { companion object { @JvmStatic fun OnEasyRTC_ConnectionStateChange_Callback(userPtr: Long, state: Int): Int { Log.d("EasyRTC", "State: $state") return 0 } @JvmStatic fun OnEasyRTC_Transceiver_Callback( userPtr: Long, type: Int, codecId: Int, frameData: ByteArray?, frameSize: Int, pts: Long, bandwidth: Int ): Int { when (type) { 0x01 -> Log.d("EasyRTC", "Video: $frameSize bytes") 0x02 -> Log.d("EasyRTC", "Audio: $frameSize bytes") } return 0 } } fun setupConnection() { // Create peer connection val pc = cn.easyrtc.peerconnection.create( version = 0, iceTransportPolicy = 2, stunServerUrl = "stun.l.google.com:19302", turnServerUrl = "", turnUsername = "", turnCredential = "", userPtr = this.hashCode().toLong() ) // Add video transceiver val videoTx = cn.easyrtc.peerconnection.AddTransceiver( peerConnection = pc, codecID = 1, // H.264 streamId = "stream0", trackId = "0", streamTrackType = 2, // VIDEO direction = 1, // SENDRECV userPtr = this.hashCode().toLong() ) // Add audio transceiver val audioTx = cn.easyrtc.peerconnection.AddTransceiver( peerConnection = pc, codecID = 2, // Opus streamId = "stream0", trackId = "1", streamTrackType = 1, // AUDIO direction = 1, // SENDRECV userPtr = this.hashCode().toLong() ) // Create offer and send via signal server cn.easyrtc.peerconnection.CreateOffer(pc, this.hashCode().toLong()) } } ``` -------------------------------- ### Peer Connection Creation Example Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/03-peer-connection.md Demonstrates implementing a state change callback and initializing a peer connection with basic ICE configuration. ```c static int connection_state_callback(void* userPtr, EASYRTC_PEER_CONNECTION_STATE state) { MyAppContext* ctx = (MyAppContext*)userPtr; switch (state) { case EASYRTC_PEER_CONNECTION_STATE_CONNECTED: printf("Connected! Ready for media.\n"); break; case EASYRTC_PEER_CONNECTION_STATE_FAILED: printf("Connection failed.\n"); break; case EASYRTC_PEER_CONNECTION_STATE_CLOSED: printf("Connection closed.\n"); break; default: printf("State: %d\n", state); } return 0; } // Create configuration EasyRTC_Configuration config = {0}; config.iceTransportPolicy = EasyRTC_ICE_TRANSPORT_POLICY_ALL; strcpy(config.iceServers[0].urls, "stun.l.google.com:19302"); // Create peer connection EasyRTC_PeerConnection pc = NULL; MyAppContext myCtx = {...}; int ret = EasyRTC_CreatePeerConnection(&pc, &config, connection_state_callback, &myCtx); if (ret != EASYRTC_ERROR_NO) { fprintf(stderr, "Failed to create peer connection\n"); } ``` -------------------------------- ### Configure Surveillance Camera Scenario Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/09-configuration.md Setup send-only media streams with relay-preferred transport. ```c EasyRTC_Configuration config = {0}; config.iceTransportPolicy = EasyRTC_ICE_TRANSPORT_POLICY_RELAY; // TURN relay only (no direct P2P) strcpy(config.iceServers[0].urls, "turn.example.com:3478?transport=tcp"); strcpy(config.iceServers[0].username, "camera_device"); strcpy(config.iceServers[0].credential, "device_secret"); // Video: H.264 send only EasyRTC_MediaStreamTrack videoTrack = {0}; videoTrack.codec = EasyRTC_CODEC_H264; videoTrack.kind = EasyRTC_MEDIA_STREAM_TRACK_KIND_VIDEO; strcpy(videoTrack.trackId, "main"); strcpy(videoTrack.streamId, "camera1"); EasyRTC_RtpTransceiverInit videoInit = {0}; videoInit.direction = EasyRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY; // Audio: Opus send only (microphone) EasyRTC_MediaStreamTrack audioTrack = {0}; audioTrack.codec = EasyRTC_CODEC_OPUS; audioTrack.kind = EasyRTC_MEDIA_STREAM_TRACK_KIND_AUDIO; strcpy(audioTrack.trackId, "mic"); strcpy(audioTrack.streamId, "camera1"); EasyRTC_RtpTransceiverInit audioInit = {0}; audioInit.direction = EasyRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY; ``` -------------------------------- ### Basic Device Setup in C Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/12-quick-start.md Initializes a minimal WebRTC device using EasyRTC, defining callbacks for connection state, media processing, and SDP signaling. ```c #include #include #include #include // Context structure to track application state typedef struct { EasyRTC_PeerConnection pc; EasyRTC_Transceiver video_tx; EasyRTC_Transceiver audio_tx; int connection_state; } AppContext; // Callback: connection state changes int state_callback(void* userPtr, EASYRTC_PEER_CONNECTION_STATE state) { AppContext* ctx = (AppContext*)userPtr; ctx->connection_state = state; printf("Connection state: %d\n", state); if (state == EASYRTC_PEER_CONNECTION_STATE_CONNECTED) { printf("Ready to send/receive media\n"); } else if (state == EASYRTC_PEER_CONNECTION_STATE_FAILED) { printf("Connection failed\n"); } return 0; } // Callback: incoming media frames int media_callback(void* userPtr, EASYRTC_TRANSCEIVER_CALLBACK_TYPE_E type, EasyRTC_CODEC codecID, EasyRTC_Frame* frame, double bandwidth) { switch (type) { case EASYRTC_TRANSCEIVER_CALLBACK_VIDEO_FRAME: printf("Received video frame: %u bytes\n", frame->size); break; case EASYRTC_TRANSCEIVER_CALLBACK_AUDIO_FRAME: printf("Received audio frame: %u bytes\n", frame->size); break; case EASYRTC_TRANSCEIVER_CALLBACK_BANDWIDTH: printf("Available bandwidth: %f bps\n", bandwidth); break; case EASYRTC_TRANSCEIVER_CALLBACK_KEY_FRAME_REQ: printf("Peer requests keyframe\n"); break; } return 0; } // Callback: SDP generated (offer/answer) int sdp_callback(void* userPtr, const int isOffer, const char* sdp) { if (sdp == NULL) { printf("SDP generation complete\n"); return 0; } if (isOffer) { printf("=== OFFER SDP (send to remote) ===\n%s\n", sdp); } else { printf("=== ANSWER SDP (send to remote) ===\n%s\n", sdp); } // TODO: Send SDP via signal server to remote peer return 0; } ``` -------------------------------- ### Implement SDP Callback and Create Offer Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/06-sdp-signaling.md Example implementation of the SDP callback to handle offer/answer transmission and the initiation of the offer creation process. ```c static int sdp_callback(void* userPtr, const int isOffer, const char* sdp) { MyAppContext* ctx = (MyAppContext*)userPtr; if (sdp == NULL) { printf("SDP generation finished\n"); return 0; } if (isOffer) { printf("Generated offer:\n%s\n", sdp); // Send via WebSocket to remote peer send_to_peer_via_websocket(ctx->ws_conn, sdp); } else { printf("Generated answer:\n%s\n", sdp); // Send via WebSocket to remote peer send_to_peer_via_websocket(ctx->ws_conn, sdp); } return 0; } // Create offer int ret = EasyRTC_CreateOffer(pc, sdp_callback, &myCtx); if (ret != EASYRTC_ERROR_NO) { fprintf(stderr, "Failed to create offer\n"); } ``` -------------------------------- ### Implement data channel callback and creation Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/05-data-channels.md Example implementation of a data channel callback and the subsequent channel creation call. ```c static int datachannel_callback(void* userPtr, EASYRTC_DATACHANNEL_CALLBACK_TYPE_E type, BOOL isBinary, const char* msgData, int msgLen) { MyAppContext* ctx = (MyAppContext*)userPtr; switch (type) { case EASYRTC_DATACHANNEL_CALLBACK_OPENED: printf("Data channel opened\n"); ctx->data_channel_ready = 1; break; case EASYRTC_DATACHANNEL_CALLBACK_DATA: if (isBinary) { // msgData is binary, msgLen is size in bytes process_binary_message(ctx, (unsigned char*)msgData, msgLen); } else { // msgData is null-terminated string (or not, check msgLen) process_text_message(ctx, msgData, msgLen); } break; } return 0; } // Create data channel EasyRTC_DataChannel dc = NULL; int ret = EasyRTC_AddDataChannel(&dc, pc, "control", datachannel_callback, &myCtx); if (ret != EASYRTC_ERROR_NO) { fprintf(stderr, "Failed to add data channel\n"); } ``` -------------------------------- ### Configure Video Conference Scenario Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/09-configuration.md Setup full duplex communication with P2P discovery and TURN fallback. ```c EasyRTC_Configuration config = {0}; config.iceTransportPolicy = EasyRTC_ICE_TRANSPORT_POLICY_ALL; // STUN server for P2P discovery strcpy(config.iceServers[0].urls, "stun.l.google.com:19302"); // TURN server fallback strcpy(config.iceServers[1].urls, "turn.example.com:3478?transport=udp"); strcpy(config.iceServers[1].username, "user"); strcpy(config.iceServers[1].credential, "pass"); // Video: H.264 full duplex EasyRTC_MediaStreamTrack videoTrack = {0}; videoTrack.codec = EasyRTC_CODEC_H264; videoTrack.kind = EasyRTC_MEDIA_STREAM_TRACK_KIND_VIDEO; strcpy(videoTrack.trackId, "0"); strcpy(videoTrack.streamId, "stream0"); EasyRTC_RtpTransceiverInit videoInit = {0}; videoInit.direction = EasyRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV; // Audio: Opus full duplex EasyRTC_MediaStreamTrack audioTrack = {0}; audioTrack.codec = EasyRTC_CODEC_OPUS; audioTrack.kind = EasyRTC_MEDIA_STREAM_TRACK_KIND_AUDIO; strcpy(audioTrack.trackId, "1"); strcpy(audioTrack.streamId, "stream0"); EasyRTC_RtpTransceiverInit audioInit = {0}; audioInit.direction = EasyRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV; ``` -------------------------------- ### Free EasyRTC Transceiver Usage Example Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/04-transceivers.md Demonstrates safe transceiver cleanup and handle nullification. ```c if (videoTransceiver != NULL) { int ret = EasyRTC_FreeTransceiver(&videoTransceiver); if (ret != EASYRTC_ERROR_NO) { fprintf(stderr, "Warning: transceiver cleanup failed\n"); } // videoTransceiver is now NULL } ``` -------------------------------- ### Example Offer SDP Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/06-sdp-signaling.md A sample Session Description Protocol (SDP) offer containing ICE credentials, fingerprint, and media descriptions. ```text v=0 o=- 123456789 2 IN IP4 192.168.1.100 s=EasyRTC t=0 0 a=ice-ufrag:xxx a=ice-pwd:yyyyy a=fingerprint:sha-256 AA:BB:CC:... m=video 9 RTP/SAVPF 96 a=rtpmap:96 H264/90000 a=fmtp:96 level-level=1 a=candidate:1 1 udp 2130706431 192.168.1.100 54321 typ host a=candidate:2 1 udp 1694498815 203.0.113.1 54322 typ srflx raddr 192.168.1.100 rport 54321 m=audio 9 RTP/SAVPF 97 a=rtpmap:97 opus/48000/2 ``` -------------------------------- ### Initialize and Manage WebRTC in Android Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/12-quick-start.md Demonstrates the lifecycle of a WebRTC peer connection, including transceiver setup, frame transmission, and callback handling for connection states and media data. ```kotlin import android.app.Activity import android.os.Bundle import android.util.Log import android.view.SurfaceView import cn.easyrtc.peerconnection class WebRTCActivity : Activity() { private var peerConnection: Long = 0 private var videoTransceiver: Long = 0 private var audioTransceiver: Long = 0 private val TAG = "WebRTC" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_webrtc) setupEasyRTC() } private fun setupEasyRTC() { // Create peer connection peerConnection = peerconnection.create( version = 0, iceTransportPolicy = 2, // Allow any ICE candidate stunServerUrl = "stun.l.google.com:19302", turnServerUrl = "turn.example.com:3478?transport=tcp", turnUsername = "user", turnCredential = "pass", userPtr = hashCode().toLong() ) Log.d(TAG, "PeerConnection created: $peerConnection") // Add video transceiver videoTransceiver = peerconnection.AddTransceiver( peerConnection = peerConnection, codecID = 1, // H.264 streamId = "stream0", trackId = "0", streamTrackType = 2, // VIDEO direction = 1, // SENDRECV userPtr = hashCode().toLong() ) Log.d(TAG, "Video transceiver added: $videoTransceiver") // Add audio transceiver audioTransceiver = peerconnection.AddTransceiver( peerConnection = peerConnection, codecID = 2, // Opus streamId = "stream0", trackId = "1", streamTrackType = 1, // AUDIO direction = 1, // SENDRECV userPtr = hashCode().toLong() ) Log.d(TAG, "Audio transceiver added: $audioTransceiver") // Create offer val ret = peerconnection.CreateOffer(peerConnection, hashCode().toLong()) if (ret != 0) { Log.e(TAG, "Failed to create offer: $ret") } else { Log.d(TAG, "Offer created successfully") } } fun sendVideoFrame(frameData: ByteArray, isKeyFrame: Boolean) { if (videoTransceiver == 0L) return val ret = peerconnection.SendVideoFrame( transceiver = videoTransceiver, frameData = frameData, frameSize = frameData.size, keyFrame = if (isKeyFrame) 1 else 0, pts = System.currentTimeMillis().toInt() ) if (ret != 0) { Log.e(TAG, "Failed to send video frame: $ret") } } fun sendAudioFrame(frameData: ByteArray) { if (audioTransceiver == 0L) return val ret = peerconnection.SendAudioFrame( transceiver = audioTransceiver, frameData = frameData, frameSize = frameData.size, pts = System.currentTimeMillis().toInt() ) if (ret != 0) { Log.e(TAG, "Failed to send audio frame: $ret") } } fun setRemoteAnswer(answerSdp: String) { val ret = peerconnection.SetRemoteDescription(peerConnection, answerSdp) if (ret != 0) { Log.e(TAG, "Failed to set remote description: $ret") } else { Log.d(TAG, "Remote SDP set successfully") } } override fun onDestroy() { super.onDestroy() if (videoTransceiver != 0L) { peerconnection.FreeTransceiver(videoTransceiver) } if (audioTransceiver != 0L) { peerconnection.FreeTransceiver(audioTransceiver) } if (peerConnection != 0L) { peerconnection.release(peerConnection) } } companion object { @JvmStatic fun OnEasyRTC_ConnectionStateChange_Callback(userPtr: Long, state: Int): Int { Log.d("WebRTC", "Connection state changed: $state") return 0 } @JvmStatic fun OnEasyRTC_Transceiver_Callback( userPtr: Long, type: Int, codecId: Int, frameData: ByteArray?, frameSize: Int, pts: Long, bandwidth: Int ): Int { when (type) { 0x01 -> Log.d("WebRTC", "Video frame: $frameSize bytes, codec=$codecId") 0x02 -> Log.d("WebRTC", "Audio frame: $frameSize bytes") 0x04 -> Log.d("WebRTC", "Bandwidth: $bandwidth bps") 0x08 -> Log.d("WebRTC", "Peer requests keyframe") } return 0 } @JvmStatic fun OnEasyRTC_IceCandidate_Callback(userPtr: Long, isOffer: Int, sdp: String?): Int { if (sdp == null) { ``` -------------------------------- ### Sending an H.264 Video Frame Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/04-transceivers.md Example demonstrating how to populate the EasyRTC_Frame structure and send it using a video transceiver. ```c // Send H.264 video frame EasyRTC_Frame frame = {0}; frame.version = 0; frame.index = frameCounter++; frame.decodingTs = currentTimeUs * 10; // 100-nanosecond units frame.presentationTs = currentTimeUs * 10; frame.duration = 33333000; // ~33ms for 30fps frame.size = encodedDataLength; frame.frameData = encodedData; frame.trackId = 0; // video track frame.flags = (isKeyframe ? EASYRTC_FRAME_FLAG_KEY_FRAME : 0) | EASYRTC_FRAME_FLAG_END_OF_FRAGMENT; int ret = EasyRTC_SendFrame(videoTransceiver, &frame); if (ret != EASYRTC_ERROR_NO) { fprintf(stderr, "Failed to send frame\n"); } ``` -------------------------------- ### Sending Text and Binary Messages Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/05-data-channels.md Examples demonstrating how to send text commands and binary control data using the EasyRTC_DataChannelSend function. ```c const char* command = "RECORD_START"; int ret = EasyRTC_DataChannelSend(dc, FALSE, command, strlen(command)); if (ret != EASYRTC_ERROR_NO) { fprintf(stderr, "Failed to send text message\n"); } ``` ```c unsigned char controlData[100]; controlData[0] = 0x01; // command ID controlData[1] = 0x00; // reserved // ... fill more data ... int dataLen = 10; int ret = EasyRTC_DataChannelSend(dc, TRUE, (const char*)controlData, dataLen); if (ret != EASYRTC_ERROR_NO) { fprintf(stderr, "Failed to send binary message\n"); } ``` -------------------------------- ### Project Directory Structure Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/00-index.md Visual representation of the EasyRTC repository layout, highlighting header files, platform-specific libraries, and example implementations. ```text easyrtc/ ├── include/ │ ├── EasyRTCAPI.h # C API header │ └── JNI_EasyRTCPeer.h # JNI header ├── lib/ │ ├── windows/ # Windows DLLs (x86, x64) │ ├── linux/ # Linux shared libraries (x86, x64) │ ├── android/ # Android NDK libraries (arm64, armv7, x86) │ └── arm/ # ARM embedded libraries ├── signalserver/ # Reference signal server source │ ├── main.cpp │ ├── signalserver.ini │ └── build.sh ├── android/ # Android example/JNI ├── windows/ # Windows example ├── linux/ # Linux example └── README.md ``` -------------------------------- ### JNI Native Pipeline Methods Source: https://github.com/easydarwin/easyrtc/blob/master/docs/superpowers/plans/2026-04-20-android-native-media-migration.md JNI interface methods for creating, starting, writing frames, reinitializing, and releasing the native video decoder pipeline. ```cpp extern "C" { JNIEXPORT jlong JNICALL Java_cn_easyrtc_media_VideoDecoderPipeline_nativeCreate( JNIEnv* env, jobject thiz, jobject surface, jint codecType, jint width, jint height) { auto* pipeline = new VideoDecoderPipeline(); pipeline->width = width; pipeline->height = height; pipeline->currentCodecType = (codecType == 6) ? "video/hevc" : "video/avc"; if (surface) { pipeline->surface = ANativeWindow_fromSurface(env, surface); } if (!initDecoder(pipeline)) { if (pipeline->surface) ANativeWindow_release(pipeline->surface); delete pipeline; return 0; } LOGD("VideoDecoderPipeline created: %s %dx%d", pipeline->currentCodecType.c_str(), width, height); return reinterpret_cast(pipeline); } JNIEXPORT void JNICALL Java_cn_easyrtc_media_VideoDecoderPipeline_nativeStart( JNIEnv* env, jobject thiz, jlong pipelinePtr) { auto* pipeline = reinterpret_cast(pipelinePtr); if (!pipeline) return; pipeline->running.store(true); int ret = pthread_create(&pipeline->decodeThread, nullptr, decodeThreadFunc, pipeline); if (ret != 0) { LOGE("Failed to create decode thread: %d", ret); pipeline->running.store(false); } } JNIEXPORT void JNICALL Java_cn_easyrtc_media_VideoDecoderPipeline_nativeWriteFrame( JNIEnv* env, jobject thiz, jlong pipelinePtr, jbyteArray data, jint size) { auto* pipeline = reinterpret_cast(pipelinePtr); if (!pipeline || !pipeline->running.load() || pipeline->destroyed.load() || size <= 0) return; jbyte* bytes = env->GetByteArrayElements(data, nullptr); if (!bytes) return; std::vector frame(bytes, bytes + size); env->ReleaseByteArrayElements(data, bytes, JNI_ABORT); { std::lock_guard lock(pipeline->queueMutex); if (pipeline->frameQueue.size() >= pipeline->MAX_QUEUE_SIZE) { pipeline->frameQueue.pop(); } pipeline->frameQueue.push(std::move(frame)); } } JNIEXPORT void JNICALL Java_cn_easyrtc_media_VideoDecoderPipeline_nativeReinit( JNIEnv* env, jobject thiz, jlong pipelinePtr, jint codecType) { auto* pipeline = reinterpret_cast(pipelinePtr); if (!pipeline) return; std::string newCodec = (codecType == 6) ? "video/hevc" : "video/avc"; if (newCodec == pipeline->currentCodecType) return; LOGD("Reinitializing video decoder: %s -> %s", pipeline->currentCodecType.c_str(), newCodec.c_str()); pipeline->running.store(false); if (pipeline->decodeThread) { pthread_join(pipeline->decodeThread, nullptr); pipeline->decodeThread = 0; } { std::lock_guard lock(pipeline->queueMutex); while (!pipeline->frameQueue.empty()) pipeline->frameQueue.pop(); } pipeline->currentCodecType = newCodec; initDecoder(pipeline); pipeline->running.store(true); int ret = pthread_create(&pipeline->decodeThread, nullptr, decodeThreadFunc, pipeline); if (ret != 0) { LOGE("Failed to restart decode thread: %d", ret); } } JNIEXPORT void JNICALL Java_cn_easyrtc_media_VideoDecoderPipeline_nativeRelease( JNIEnv* env, jobject thiz, jlong pipelinePtr) { auto* pipeline = reinterpret_cast(pipelinePtr); if (!pipeline) return; pipeline->running.store(false); pipeline->destroyed.store(true); if (pipeline->decodeThread) { pthread_join(pipeline->decodeThread, nullptr); pipeline->decodeThread = 0; } ``` -------------------------------- ### Implement and initialize a transceiver Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/04-transceivers.md Demonstrates a callback implementation for processing frames and bandwidth, followed by the initialization of a video transceiver. ```c static int transceiver_callback(void* userPtr, EASYRTC_TRANSCEIVER_CALLBACK_TYPE_E type, EasyRTC_CODEC codecID, EasyRTC_Frame* frame, double bandwidthEstimation) { MyAppContext* ctx = (MyAppContext*)userPtr; switch (type) { case EASYRTC_TRANSCEIVER_CALLBACK_VIDEO_FRAME: // frame->frameData contains raw H.264/VP8/H.265 data // frame->size is in bytes process_video_frame(ctx, frame->frameData, frame->size); break; case EASYRTC_TRANSCEIVER_CALLBACK_AUDIO_FRAME: // frame->frameData contains raw Opus/MULAW/ALAW/AAC data process_audio_frame(ctx, frame->frameData, frame->size); break; case EASYRTC_TRANSCEIVER_CALLBACK_BANDWIDTH: printf("Available bandwidth: %f bps\n", bandwidthEstimation); ctx->current_bandwidth = bandwidthEstimation; break; case EASYRTC_TRANSCEIVER_CALLBACK_KEY_FRAME_REQ: printf("Peer requests keyframe\n"); ctx->force_next_frame_key = 1; break; } return 0; } // Create video transceiver EasyRTC_MediaStreamTrack videoTrack = { .codec = EasyRTC_CODEC_H264, .trackId = "0", .streamId = "stream0", .kind = EasyRTC_MEDIA_STREAM_TRACK_KIND_VIDEO }; EasyRTC_RtpTransceiverInit init = { .direction = EasyRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV }; EasyRTC_Transceiver videoTx = NULL; int ret = EasyRTC_AddTransceiver(&videoTx, pc, &videoTrack, &init, transceiver_callback, &myCtx); if (ret != EASYRTC_ERROR_NO) { fprintf(stderr, "Failed to add video transceiver\n"); } ``` -------------------------------- ### Send Binary Telemetry Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/05-data-channels.md Example of sending a binary struct representing sensor data. ```c struct { float temperature; float humidity; } telemetry; telemetry.temperature = 23.5f; telemetry.humidity = 45.2f; EasyRTC_DataChannelSend(dc, TRUE, (const char*)&telemetry, sizeof(telemetry)); ``` -------------------------------- ### Send Text Message Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/05-data-channels.md Example of sending a JSON command string over a data channel. ```c const char* json = "{\"action\": \"zoom\", \"level\": 2}"; EasyRTC_DataChannelSend(dc, FALSE, json, strlen(json)); ``` -------------------------------- ### Typical EasyRTC initialization pattern Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/02-initialization.md Demonstrates the standard lifecycle of initializing and deinitializing the library within a main function. ```c #include int main() { // Initialize if (EasyRTC_Init() != EASYRTC_ERROR_NO) { fprintf(stderr, "Failed to init EasyRTC\n"); return 1; } // ... create connections, exchange SDP, send/receive media ... // Deinitialize if (EasyRTC_Deinit() != EASYRTC_ERROR_NO) { fprintf(stderr, "Warning: EasyRTC deinit failed\n"); } return 0; } ``` -------------------------------- ### Configure STUN server Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/09-configuration.md Initialize the configuration and assign a STUN server for peer discovery. ```c EasyRTC_Configuration config = {0}; config.iceTransportPolicy = EasyRTC_ICE_TRANSPORT_POLICY_ALL; EasyRTC_IceServer stun = {0}; strcpy(stun.urls, "stun.l.google.com:19302"); config.iceServers[0] = stun; ``` -------------------------------- ### Media Direction Modes Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/09-configuration.md Examples of setting different media flow directions for an RTP transceiver. ```c EasyRTC_RtpTransceiverInit init = {0}; init.direction = EasyRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV; // Transceiver sends local media AND receives remote media // Used for: video/audio conferencing, real-time communication ``` ```c EasyRTC_RtpTransceiverInit init = {0}; init.direction = EasyRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY; // Transceiver sends local media; ignores remote media // Used for: broadcasting (camera → multiple viewers), surveillance ``` ```c EasyRTC_RtpTransceiverInit init = {0}; init.direction = EasyRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY; // Transceiver receives remote media; does not send // Used for: playback/monitoring, viewer-only clients ``` ```c EasyRTC_RtpTransceiverInit init = {0}; init.direction = EasyRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE; // Transceiver disabled; no media flows // Used for: placeholder tracks, conditional media (enabled later) ``` -------------------------------- ### Free Data Channel Usage Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/05-data-channels.md Example of safely freeing a data channel and checking for errors. ```c if (dc != NULL) { int ret = EasyRTC_FreeDataChannel(&dc); if (ret != EASYRTC_ERROR_NO) { fprintf(stderr, "Warning: datachannel cleanup failed\n"); } // dc is now NULL } ``` -------------------------------- ### Configure TURN server with authentication Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/09-configuration.md Add a TURN server to the configuration, including required credentials for relaying media. ```c EasyRTC_Configuration config = {0}; config.iceTransportPolicy = EasyRTC_ICE_TRANSPORT_POLICY_ALL; // STUN server EasyRTC_IceServer stun = {0}; strcpy(stun.urls, "stun.l.google.com:19302"); config.iceServers[0] = stun; // TURN server with authentication EasyRTC_IceServer turn = {0}; strcpy(turn.urls, "turn.example.com:3478?transport=udp"); strcpy(turn.username, "alice"); strcpy(turn.credential, "secret_password"); config.iceServers[1] = turn; ``` -------------------------------- ### Initialize and Configure EasyRTC Device Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/12-quick-start.md Initializes the library, sets up ICE servers, adds video and audio transceivers, and simulates sending media frames. ```c int main(int argc, char* argv[]) { // Initialize EasyRTC if (EasyRTC_Init() != EASYRTC_ERROR_NO) { fprintf(stderr, "Failed to initialize EasyRTC\n"); return 1; } // Setup context AppContext ctx = {0}; // Configure ICE (STUN/TURN) EasyRTC_Configuration config = {0}; config.iceTransportPolicy = EasyRTC_ICE_TRANSPORT_POLICY_ALL; strcpy(config.iceServers[0].urls, "stun.l.google.com:19302"); // Create peer connection if (EasyRTC_CreatePeerConnection(&ctx.pc, &config, state_callback, &ctx) != EASYRTC_ERROR_NO) { fprintf(stderr, "Failed to create peer connection\n"); return 1; } // Add video transceiver (H.264) EasyRTC_MediaStreamTrack video_track = {0}; video_track.codec = EasyRTC_CODEC_H264; video_track.kind = EasyRTC_MEDIA_STREAM_TRACK_KIND_VIDEO; strcpy(video_track.trackId, "0"); strcpy(video_track.streamId, "stream0"); EasyRTC_RtpTransceiverInit video_init = {0}; video_init.direction = EasyRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV; if (EasyRTC_AddTransceiver(&ctx.video_tx, ctx.pc, &video_track, &video_init, media_callback, &ctx) != EASYRTC_ERROR_NO) { fprintf(stderr, "Failed to add video transceiver\n"); return 1; } // Add audio transceiver (Opus) EasyRTC_MediaStreamTrack audio_track = {0}; audio_track.codec = EasyRTC_CODEC_OPUS; audio_track.kind = EasyRTC_MEDIA_STREAM_TRACK_KIND_AUDIO; strcpy(audio_track.trackId, "1"); strcpy(audio_track.streamId, "stream0"); EasyRTC_RtpTransceiverInit audio_init = {0}; audio_init.direction = EasyRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV; if (EasyRTC_AddTransceiver(&ctx.audio_tx, ctx.pc, &audio_track, &audio_init, media_callback, &ctx) != EASYRTC_ERROR_NO) { fprintf(stderr, "Failed to add audio transceiver\n"); return 1; } // Create offer if (EasyRTC_CreateOffer(ctx.pc, sdp_callback, &ctx) != EASYRTC_ERROR_NO) { fprintf(stderr, "Failed to create offer\n"); return 1; } // Simulate sending encoded frames printf("Sending media frames...\n"); for (int i = 0; i < 10; i++) { // In real app, get from video encoder unsigned char frame_data[1024] = {0}; EasyRTC_Frame frame = {0}; frame.version = 0; frame.index = i; frame.flags = (i == 0 ? EASYRTC_FRAME_FLAG_KEY_FRAME : 0) | EASYRTC_FRAME_FLAG_END_OF_FRAGMENT; frame.size = sizeof(frame_data); frame.frameData = frame_data; frame.trackId = 0; // video frame.decodingTs = i * 33333000; // ~33ms per frame frame.presentationTs = i * 33333000; frame.duration = 33333000; EasyRTC_SendFrame(ctx.video_tx, &frame); sleep(1); } // Cleanup if (ctx.video_tx) EasyRTC_FreeTransceiver(&ctx.video_tx); if (ctx.audio_tx) EasyRTC_FreeTransceiver(&ctx.audio_tx); if (ctx.pc) EasyRTC_ReleasePeerConnection(&ctx.pc); EasyRTC_Deinit(); return 0; } ``` -------------------------------- ### cn.easyrtc.peerconnection.create Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/11-jni-android.md Initializes a new peer connection instance with specified STUN/TURN server configurations. ```APIDOC ## cn.easyrtc.peerconnection.create ### Description Creates a new peer connection object for WebRTC communication. ### Parameters - **version** (int) - Required - Protocol version. - **iceTransportPolicy** (int) - Required - ICE transport policy. - **stunServerUrl** (string) - Required - STUN server address. - **turnServerUrl** (string) - Optional - TURN server address. - **turnUsername** (string) - Optional - TURN authentication username. - **turnCredential** (string) - Optional - TURN authentication password. - **userPtr** (long) - Required - User pointer for callback identification. ``` -------------------------------- ### Create an Android Virtual Device Source: https://github.com/easydarwin/easyrtc/blob/master/docs/dump_frames.qa.txt Command-line utility to create a new AVD if none are available. ```bash avdmanager create avd -n test -k "system-images;android-28;default;arm64-v8a" ``` -------------------------------- ### Get ICE Candidate Pair Statistics Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/03-peer-connection.md Retrieves connectivity statistics for the active ICE candidate pair. Must be called only after the connection state is CONNECTED. ```c int EASYRTC_API EasyRTC_GetIceCandidatePairStats( EasyRTC_PeerConnection pPeerConnection, EasyRTC_IceCandidatePairStats* pRtcIceCandidatePairStats ); ``` ```c EasyRTC_IceCandidatePairStats stats = {0}; int ret = EasyRTC_GetIceCandidatePairStats(pc, &stats); if (ret == EASYRTC_ERROR_NO) { printf("Local candidate type: %d\n", stats.local_iceCandidateType); printf("Remote candidate type: %d\n", stats.remote_iceCandidateType); printf("RTT: %f ms\n", stats.currentRoundTripTime * 1000); printf("Available outgoing bitrate: %f bps\n", stats.availableOutgoingBitrate); } ``` -------------------------------- ### Manage Android Emulators Source: https://github.com/easydarwin/easyrtc/blob/master/docs/dump_frames.qa.txt Commands to list available AVDs and launch a specific emulator instance. ```bash adb devices # Or list AVD names $ANDROID_HOME/emulator/emulator -list-avds # Launch one $ANDROID_HOME/emulator/emulator -avd & ``` -------------------------------- ### Deploy and Run Android CLI Source: https://github.com/easydarwin/easyrtc/blob/master/docs/superpowers/specs/2026-04-18-android-cli-design.md Commands to push the binary and library to an Android device and execute the CLI tool. ```bash adb push easyrtc_open /data/local/tmp/ adb push libEasyRTC.so /data/local/tmp/ adb shell "cd /data/local/tmp && chmod +x easyrtc_open && LD_LIBRARY_PATH=. ./easyrtc_open " ``` -------------------------------- ### Project File Structure Source: https://github.com/easydarwin/easyrtc/blob/master/docs/superpowers/specs/2026-04-20-android-native-media-migration-design.md Directory layout for the EasyRTC native media migration, detailing new and modified C++ and Java/Kotlin components. ```text easyrtc/src/main/cpp/ easyrtc_common.h # Shared: logging, EasyRTCAPI.h include, constants easyrtc_media.h # Existing: MediaPipeline struct (modified) easyrtc_media.cpp # Existing: Camera+Encoder (modified: remove dlsym) easyrtc_audio.h # NEW: AudioCapturePipeline struct easyrtc_audio.cpp # NEW: AAudio recording → EasyRTC_SendFrame easyrtc_audio_playback.h # NEW: AudioPlaybackPipeline struct easyrtc_audio_playback.cpp # NEW: Transceiver audio callback → AAudio playback easyrtc_video_decoder.h # NEW: VideoDecoderPipeline struct easyrtc_video_decoder.cpp # NEW: Transceiver video callback → NDK MediaCodec decoder easyrtc_session.h # NEW: Top-level MediaSession — owns all pipelines, registers transceivers easyrtc_session.cpp # NEW: Creates transceivers via EasyRTC_AddTransceiver, routes callbacks CMakeLists.txt # Updated: all new sources, include path, link aaudio + EasyRTC easyrtc/src/main/java/cn/easyrtc/ peerconnection.java # Simplified: remove media-related native methods EasyRTCSdk.kt # Simplified: remove AddTransceiver/SendFrame, add createMediaSession media/MediaPipeline.kt # Existing (unchanged) media/MediaSession.kt # NEW: Thin Kotlin wrapper for native MediaSession app/src/main/java/.../fragment/ HomeFragment.kt # Modified: use MediaSession for all media ``` -------------------------------- ### Add Media Transceiver via JNI Source: https://github.com/easydarwin/easyrtc/blob/master/_autodocs/11-jni-android.md Defines the native method signature for adding audio or video transceivers and provides Kotlin usage examples for both media types. ```java public static native long AddTransceiver( long peerConnection, // PeerConnection handle int codecID, // 1=H264, 2=OPUS, 3=VP8, 4=MULAW, 5=ALAW, 7=H265, 8=AAC String streamId, // Stream identifier String trackId, // Track identifier int streamTrackType, // 1=AUDIO, 2=VIDEO int direction, // 1=SENDRECV, 2=SENDONLY, 3=RECVONLY, 4=INACTIVE long userPtr // User context for callbacks ); ``` ```kotlin // Add H.264 video transceiver val videoTransceiver = cn.easyrtc.peerconnection.AddTransceiver( peerConnection = pc, codecID = 1, // H.264 streamId = "stream0", trackId = "0", streamTrackType = 2, // VIDEO direction = 1, // SENDRECV userPtr = this.hashCode().toLong() ) // Add Opus audio transceiver val audioTransceiver = cn.easyrtc.peerconnection.AddTransceiver( peerConnection = pc, codecID = 2, // Opus streamId = "stream0", trackId = "1", streamTrackType = 1, // AUDIO direction = 1, // SENDRECV userPtr = this.hashCode().toLong() ) ```