欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

使用Android實現(xiàn)實時視頻通話(附源碼)

 更新時間:2025年04月29日 09:02:53   作者:Katie。  
在移動互聯(lián)網(wǎng)時代,實時視頻通話已成為社交,協(xié)作,教育,醫(yī)療等多種場景的標配功能,本項目將從零搭建一個基于?WebRTC?的?Android?視頻通話程序,希望對大家有一定的幫助

一、項目介紹

在移動互聯(lián)網(wǎng)時代,實時視頻通話已成為社交、協(xié)作、教育、醫(yī)療等多種場景的標配功能。要實現(xiàn)一個高質(zhì)量的 Android 視頻通話功能,需要解決視頻采集、編解碼、網(wǎng)絡(luò)傳輸、信令協(xié)商、回聲消除、網(wǎng)絡(luò)抖動控制等多方面難點。本項目將從零搭建一個基于 WebRTC 的 Android 視頻通話示例,具備以下能力:

  • 雙端互通:Android ↔ Android、Android ↔ Web(或 iOS)
  • 視頻采集與渲染:使用 Camera2 API + OpenGL 渲染本地圖像
  • 音頻處理:自動回聲消除(AEC)、自動增益控制(AGC)、噪聲抑制(NS)
  • 網(wǎng)絡(luò)傳輸:基于 UDP 的 SRTP 加密通道,支持 STUN/TURN 穿透
  • 信令交換:WebSocket 實現(xiàn) SDP 協(xié)商與 ICE 候選交換
  • 自適應(yīng)網(wǎng)絡(luò):實時監(jiān)測丟包率、往返時延,動態(tài)調(diào)整發(fā)送分辨率與碼率
  • 可選第三方集成:對接 Agora、騰訊云 TRTC、阿里云 RTC 等商用 SDK

二、相關(guān)知識

1.WebRTC 概覽

  • PeerConnection:核心接口,負責 SDP 協(xié)商、ICE 連接、SRTP 加解密
  • MediaStream:管理一組音視頻軌道(VideoTrack、AudioTrack)
  • SurfaceViewRenderer / GLSurfaceView:視頻渲染控件

2.視頻采集

  • Camera2 API:支持高分辨率、手動對焦,但回調(diào)復雜
  • WebRTC’s CameraCapturer:封裝了舊 Camera API 與 Camera2,支持前后攝切換

3.音頻處理

  • WebRTC 內(nèi)置 AEC、AGC、NS,無需額外集成
  • 可通過 AudioProcessing 接口調(diào)節(jié)參數(shù)

4.信令與 NAT 穿透

  • SDP Offer/Answer:描述音視頻能力與網(wǎng)絡(luò)參數(shù)
  • ICE Candidate:傳輸候選地址,實現(xiàn) P2P 連接
  • STUN/TURN:開啟 IceServer,解決私網(wǎng)直連問題

5.網(wǎng)絡(luò)自適應(yīng)

  • 通過 BitrateObserver 與 ConnectionStateChange 回調(diào)監(jiān)測網(wǎng)絡(luò)狀況
  • 實時調(diào)整 VideoEncoder 的目標碼率與分辨率

6.第三方 SDK 對比

  • Agora/騰訊云/阿里云:提供更高層封裝,內(nèi)置信令與跨平臺適配
  • WebRTC 原生:免費、可深度定制,但需自行搭建信令與 TURN 服務(wù)

三、實現(xiàn)思路

1.集成 WebRTC Native

  • 在 settings.gradle 中添加 webrtc 源碼或使用編譯好的 AAR
  • 初始化 PeerConnectionFactory,啟用硬件編碼/解碼

2.UI 設(shè)計

  • 兩個 SurfaceViewRenderer:本地預(yù)覽與遠端畫面
  • 控制按鈕:發(fā)起呼叫、掛斷、切換攝像頭、靜音、鏡像開關(guān)

3.信令模塊

  • 使用 WebSocket 與信令服務(wù)器通信
  • 定義簡單協(xié)議:{"type":"offer","sdp":...}、{"type":"answer",...}、{"type":"candidate",...}

4.P2P 連接流程

  • A 端點擊“呼叫”→創(chuàng)建 offer → 發(fā)送給 B 端
  • B 端收到 → 設(shè)置 remoteDesc → 創(chuàng)建 answer → 發(fā)送給 A
  • 雙方相互交換 ICE candidate → 觸發(fā) onIceConnectionChange = CONNECTED

5.音視頻采集與渲染

  • 使用 Camera2Enumerator 初始化 VideoCapturer,創(chuàng)建 VideoSource
  • peerConnection.addTrack() 添加視頻與音頻軌道
  • 遠端軌道通過 RemoteVideoTrack.addSink(remoteRenderer) 渲染

6.網(wǎng)絡(luò)優(yōu)化

  • 在 onAddTrack 中設(shè)置 AdaptiveVideoTrackSource 監(jiān)聽網(wǎng)絡(luò)帶寬
  • 動態(tài)調(diào)用 peerConnection.getSenders().find { it.track is VideoTrack }.setParameters()

7.服務(wù)端搭建

  • Node.js + ws 庫實現(xiàn)信令轉(zhuǎn)發(fā)
  • STUN:stun:stun.l.google.com:19302;TURN:自行部署或租用

四、環(huán)境與依賴

// app/build.gradle
plugins {
  id 'com.android.application'
  id 'kotlin-android'
}
 
android {
  compileSdkVersion 34
  defaultConfig {
    applicationId "com.example.videocall"
    minSdkVersion 21
    targetSdkVersion 34
    // 需啟用對攝像頭、麥克風權(quán)限
  }
  buildFeatures { viewBinding true }
  kotlinOptions { jvmTarget = "1.8" }
}
 
dependencies {
  implementation 'org.webrtc:google-webrtc:1.0.32006' // 官方 AAR
  implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
  implementation 'com.squareup.okhttp3:okhttp:4.10.0'    // WebSocket
}

五、整合代碼

// =======================================================
// 文件: AndroidManifest.xml
// 描述: 攝像頭與麥克風權(quán)限
// =======================================================
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.videocall">
  <uses-permission android:name="android.permission.CAMERA"/>
  <uses-permission android:name="android.permission.RECORD_AUDIO"/>
  <uses-permission android:name="android.permission.INTERNET"/>
  <application>
    <activity android:name=".MainActivity"
        android:theme="@style/Theme.AppCompat.NoActionBar"
        android:exported="true">
      <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
      </intent-filter>
    </activity>
  </application>
</manifest>
 
// =======================================================
// 文件: res/layout/activity_main.xml
// 描述: 本地與遠端畫面 + 控制按鈕
// =======================================================
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent" android:layout_height="match_parent">
 
  <!-- 遠端畫面 -->
  <org.webrtc.SurfaceViewRenderer
      android:id="@+id/remoteView"
      android:layout_width="match_parent"
      android:layout_height="match_parent"/>
 
  <!-- 本地預(yù)覽(右上角小窗口) -->
  <org.webrtc.SurfaceViewRenderer
      android:id="@+id/localView"
      android:layout_width="120dp"
      android:layout_height="160dp"
      android:layout_margin="16dp"
      android:layout_gravity="top|end"/>
 
  <!-- 按鈕欄 -->
  <LinearLayout
      android:orientation="horizontal"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_gravity="bottom|center"
      android:gravity="center"
      android:padding="16dp">
 
    <Button android:id="@+id/btnCall"
        android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:text="呼叫"/>
    <Button android:id="@+id/btnHangup"
        android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:text="掛斷" android:layout_marginStart="16dp"/>
    <Button android:id="@+id/btnSwitch"
        android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:text="切換攝像頭" android:layout_marginStart="16dp"/>
  </LinearLayout>
</FrameLayout>
 
// =======================================================
// 文件: SignalingClient.kt
// 描述: WebSocket 信令客戶端
// =======================================================
package com.example.videocall
 
import kotlinx.coroutines.*
import okhttp3.*
import org.json.JSONObject
import java.util.concurrent.TimeUnit
 
class SignalingClient(
  private val serverUrl: String,
  private val listener: Listener
) : WebSocketListener() {
 
  interface Listener {
    fun onOffer(sdp: String)
    fun onAnswer(sdp: String)
    fun onCandidate(sdpMid: String, sdpMLineIndex: Int, candidate: String)
  }
 
  private val client = OkHttpClient.Builder()
    .connectTimeout(10, TimeUnit.SECONDS)
    .build()
  private var ws: WebSocket? = null
 
  fun connect() {
    val req = Request.Builder().url(serverUrl).build()
    ws = client.newWebSocket(req, this)
  }
  fun close() { ws?.close(1000, "bye") }
 
  fun sendOffer(sdp: String) {
    val obj = JSONObject().apply {
      put("type", "offer"); put("sdp", sdp)
    }
    ws?.send(obj.toString())
  }
  fun sendAnswer(sdp: String) {
    val obj = JSONObject().apply {
      put("type", "answer"); put("sdp", sdp)
    }
    ws?.send(obj.toString())
  }
  fun sendCandidate(c: PeerConnection.IceCandidate) {
    val obj = JSONObject().apply {
      put("type", "candidate")
      put("sdpMid", c.sdpMid); put("sdpMLineIndex", c.sdpMLineIndex)
      put("candidate", c.sdp)
    }
    ws?.send(obj.toString())
  }
 
  override fun onMessage(webSocket: WebSocket, text: String) {
    val obj = JSONObject(text)
    when (obj.getString("type")) {
      "offer" -> listener.onOffer(obj.getString("sdp"))
      "answer"-> listener.onAnswer(obj.getString("sdp"))
      "candidate"-> listener.onCandidate(
        obj.getString("sdpMid"), obj.getInt("sdpMLineIndex"),
        obj.getString("candidate")
      )
    }
  }
}
 
// =======================================================
// 文件: MainActivity.kt
// 描述: 核心視頻通話邏輯
// =======================================================
package com.example.videocall
 
import android.Manifest
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import com.example.videocall.databinding.ActivityMainBinding
import kotlinx.coroutines.*
import org.webrtc.*
 
class MainActivity : AppCompatActivity(), SignalingClient.Listener {
 
  private lateinit var binding: ActivityMainBinding
 
  // WebRTC
  private lateinit var peerFactory: PeerConnectionFactory
  private var peerConnection: PeerConnection? = null
  private lateinit var localVideoSource: VideoSource
  private lateinit var localAudioSource: AudioSource
  private lateinit var localVideoTrack: VideoTrack
  private lateinit var localAudioTrack: AudioTrack
  private lateinit var videoCapturer: VideoCapturer
 
  private lateinit var signalingClient: SignalingClient
  private val coroutineScope = CoroutineScope(Dispatchers.Main)
 
  override fun onCreate(s: Bundle?) {
    super.onCreate(s)
    binding = ActivityMainBinding.inflate(layoutInflater)
    setContentView(binding.root)
 
    // 1. 權(quán)限申請
    ActivityCompat.requestPermissions(this,
      arrayOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO), 1)
 
    // 2. 初始化 PeerConnectionFactory
    PeerConnectionFactory.initialize(
      PeerConnectionFactory.InitializationOptions.builder(this)
        .createInitializationOptions()
    )
    peerFactory = PeerConnectionFactory.builder().createPeerConnectionFactory()
 
    // 3. 初始化本地采集與渲染
    initLocalMedia()
 
    // 4. 初始化信令
    signalingClient = SignalingClient("wss://your.signaling.server", this)
    signalingClient.connect()
 
    // 5. 按鈕事件
    binding.btnCall.setOnClickListener { startCall() }
    binding.btnHangup.setOnClickListener { hangUp() }
    binding.btnSwitch.setOnClickListener { switchCamera() }
  }
 
  private fun initLocalMedia() {
    // SurfaceViewRenderer 初始化
    binding.localView.init(EglBase.create().eglBaseContext, null)
    binding.remoteView.init(EglBase.create().eglBaseContext, null)
 
    // 攝像頭捕獲
    val enumerator = Camera2Enumerator(this)
    val camName = enumerator.deviceNames[0]
    videoCapturer = enumerator.createCapturer(camName, null)
    val surfaceTextureHelper = SurfaceTextureHelper.create("CaptureThread",
      EglBase.create().eglBaseContext)
    localVideoSource = peerFactory.createVideoSource(videoCapturer.isScreencast)
    videoCapturer.initialize(surfaceTextureHelper, this, localVideoSource.capturerObserver)
    videoCapturer.startCapture(1280, 720, 30)
 
    localVideoTrack = peerFactory.createVideoTrack("ARDAMSv0", localVideoSource)
    localVideoTrack.addSink(binding.localView)
 
    localAudioSource = peerFactory.createAudioSource(MediaConstraints())
    localAudioTrack = peerFactory.createAudioTrack("ARDAMSa0", localAudioSource)
  }
 
  private fun createPeerConnection() {
    val iceServers = listOf(
      PeerConnection.IceServer.builder("stun:stun.l.google.com:19302").createIceServer()
    )
    val rtcConfig = PeerConnection.RTCConfiguration(iceServers).apply {
      continualGatheringPolicy = PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY
    }
    peerConnection = peerFactory.createPeerConnection(rtcConfig, object : PeerConnection.Observer {
      override fun onIceCandidate(c: IceCandidate) {
        signalingClient.sendCandidate(c)
      }
      override fun onAddStream(stream: MediaStream) {
        runOnUiThread {
          stream.videoTracks[0].addSink(binding.remoteView)
        }
      }
      override fun onConnectionChange(newState: PeerConnection.PeerConnectionState) {
        Log.d("PC", "State = $newState")
      }
      // 省略其他回調(diào)
      override fun onIceConnectionChange(state: PeerConnection.IceConnectionState) {}
      override fun onIceGatheringChange(state: PeerConnection.IceGatheringState) {}
      override fun onSignalingChange(state: PeerConnection.SignalingState) {}
      override fun onIceCandidatesRemoved(candidates: Array<out IceCandidate>?) {}
      override fun onRemoveStream(stream: MediaStream?) {}
      override fun onDataChannel(dc: DataChannel?) {}
      override fun onRenegotiationNeeded() {}
      override fun onTrack(transceiver: RtpTransceiver?) {}
    })
    // 添加音視頻軌道
    peerConnection?.addTrack(localVideoTrack)
    peerConnection?.addTrack(localAudioTrack)
  }
 
  private fun startCall() {
    createPeerConnection()
    peerConnection?.createOffer(object : SdpObserver {
      override fun onCreateSuccess(desc: SessionDescription) {
        peerConnection?.setLocalDescription(this, desc)
        signalingClient.sendOffer(desc.description)
      }
      override fun onSetSuccess() {}
      override fun onCreateFailure(e: String) { }
      override fun onSetFailure(e: String) { }
    }, MediaConstraints())
  }
 
  private fun hangUp() {
    peerConnection?.close(); peerConnection = null
    signalingClient.close()
  }
 
  private fun switchCamera() {
    (videoCapturer as CameraVideoCapturer).switchCamera(null)
  }
 
  // ===== SignalingClient.Listener 回調(diào) =====
  override fun onOffer(sdp: String) {
    if (peerConnection == null) createPeerConnection()
    val offer = SessionDescription(SessionDescription.Type.OFFER, sdp)
    peerConnection?.setRemoteDescription(object: SdpObserver {
      override fun onSetSuccess() {
        peerConnection?.createAnswer(object : SdpObserver {
          override fun onCreateSuccess(desc: SessionDescription) {
            peerConnection?.setLocalDescription(this, desc)
            signalingClient.sendAnswer(desc.description)
          }
          override fun onSetSuccess() {}
          override fun onCreateFailure(e: String) {}
          override fun onSetFailure(e: String) {}
        }, MediaConstraints())
      }
      override fun onCreateSuccess(p0: SessionDescription?) {}
      override fun onCreateFailure(p0: String?) {}
      override fun onSetFailure(p0: String?) {}
    }, offer)
  }
 
  override fun onAnswer(sdp: String) {
    val answer = SessionDescription(SessionDescription.Type.ANSWER, sdp)
    peerConnection?.setRemoteDescription(object: SdpObserver {
      override fun onSetSuccess() {}
      override fun onCreateSuccess(p0: SessionDescription?) {}
      override fun onCreateFailure(p0: String?) {}
      override fun onSetFailure(p0: String?) {}
    }, answer)
  }
 
  override fun onCandidate(sdpMid: String, sdpMLineIndex: Int, cand: String) {
    val candidate = IceCandidate(sdpMid, sdpMLineIndex, cand)
    peerConnection?.addIceCandidate(candidate)
  }
}
 
// =======================================================
// 文件: SdpObserver.kt
// 描述: 簡化版 SdpObserver
// =======================================================
package com.example.videocall
 
import org.webrtc.SdpObserver
import org.webrtc.SessionDescription
 
abstract class SimpleSdpObserver : SdpObserver {
  override fun onCreateSuccess(desc: SessionDescription?) {}
  override fun onSetSuccess() {}
  override fun onCreateFailure(error: String?) {}
  override fun onSetFailure(error: String?) {}
}

六、代碼解讀

1.權(quán)限申請

動態(tài)獲取攝像頭與麥克風權(quán)限,授權(quán)后再初始化 WebRTC。

2.PeerConnectionFactory

  • PeerConnectionFactory.initialize 配置全局環(huán)境;
  • createPeerConnectionFactory 生成工廠,負責音視頻源與底層網(wǎng)絡(luò)棧。

3.本地采集與渲染

  • 使用 Camera2Enumerator 建議先試舊 API 擴展兼容;
  • SurfaceViewRenderer.init 必須在 EGLContext 已創(chuàng)建后執(zhí)行;
  • VideoCapturer.startCapture 啟動實時采集并推送給 VideoSource。

4.信令交互

  • 簡單的 JSON 協(xié)議,WebSocket 單一通道,適合小規(guī)模 Demo;
  • 生產(chǎn)環(huán)境推薦加入鑒權(quán)、重連、消息隊列等穩(wěn)定性設(shè)計。

5.P2P 與 NAT 穿透

  • 僅 STUN 無法解決對等雙方均在內(nèi)網(wǎng)的場景,需要 TURN 服務(wù)器轉(zhuǎn)發(fā)流量;
  • rtcConfig 中可添加多個 IceServer。

6.通話控制

  • “呼叫”建立 PeerConnection 并創(chuàng)建 Offer;
  • “掛斷”需同時關(guān)閉 PeerConnection、信令通道,并釋放本地資源。

七、性能與優(yōu)化

1.硬件編碼/解碼

WebRTC 默認開啟硬編硬解,可在 PeerConnectionFactory 構(gòu)建時通過選項調(diào)整。

2.自適應(yīng)碼率

監(jiān)聽 StatsObserver 中的 googAvailableSendBandwidth,動態(tài)調(diào)用

val parameters = sender.parameters
parameters.encodings[0].maxBitrateBps = newRate
sender.parameters = parameters

3.多路視頻

可同時拉取多路流(如屏幕共享 + 攝像頭),需創(chuàng)建多個 RtpSender。

4.回聲消除與音量平衡

使用 WebRTC 默認 AEC、AGC;對特殊場景可開啟軟件回聲消除器。

5.流量加密

SRTP 默認開啟;如需更高安全,可在 UDP 之上再套 TLS 隧道。

八、項目總結(jié)與拓展

本文通過原生 WebRTC示例,完整演示了 Android 實現(xiàn)實時視頻通話的全部流程:從權(quán)限、工廠初始化、攝像頭采集、信令交互到 P2P 建連和動態(tài) 網(wǎng)絡(luò)優(yōu)化。你可以進一步擴展:

屏幕共享:通過 VideoCapturerAndroid.createScreenCapturer() 或 MediaProjection 接口,實現(xiàn)應(yīng)用內(nèi)屏幕推流

多人通話:引入多路混流或 SFU(如 Janus、Jitsi、MediaSoup)

可視化統(tǒng)計:UI 上展示丟包率、幀率、往返時延、碼率曲線

第三方 SDK 對接:將 WebRTC 與 Agora/騰訊 TRTC 結(jié)合,支持更完善的商用功能

Compose 重構(gòu):將渲染視圖和控件切換到 Jetpack Compose

九、常見問題

Q1:WebRTC AAR 如何集成?

A1:直接在 Gradle 中添加 implementation 'org.webrtc:google-webrtc:1.0.32006',無需自行編譯。

Q2:信令服務(wù)器能否用 Socket.io?

A2:可以,用 socket.io-client 與 Node.js 服務(wù)端互通;注意跨域與二進制消息格式。

Q3:如何避免攝像頭沖突?

A3:在開始采集前檢查 videoCapturer != null,并在 onDestroy 中調(diào)用 stopCapture() 和 dispose()。

Q4:視頻通話質(zhì)量差怎么辦?

A4:開啟自適應(yīng)碼率、調(diào)整編碼分辨率,或增加 TURN 服務(wù)器數(shù)量降低丟包。

Q5:如何實現(xiàn)跨平臺互通?

A5:Web 端可使用 adapter.js,iOS 使用 WebRTC.framework,統(tǒng)一信令與 ICE 配置即可互通。

以上就是使用Android實現(xiàn)實時視頻通話(附源碼)的詳細內(nèi)容,更多關(guān)于Android視頻通話的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論