Google은 흑인 공동체를 위한 인종 간 평등을 진전시키기 위해 노력하고 있습니다. Google에서 어떤 노력을 하고 있는지 확인하세요.

세션 시작 프로토콜 개요

Android는 세션 시작 프로토콜(SIP)을 지원하는 API를 제공합니다. 이를 통해 SIP 기반 인터넷 전화 통신 기능을 애플리케이션에 추가할 수 있습니다. Android에는 전체 SIP 프로토콜 스택 및 통합 통화 관리 서비스가 포함되어 있어 애플리케이션이 세션, 전송 수준 통신 또는 오디오 녹음 또는 재생을 직접 관리할 필요 없이 발신 및 수신 음성 통화를 쉽게 설정할 수 있습니다.

SIP API를 사용할 수 있는 애플리케이션 유형의 예는 다음과 같습니다.

  • 화상 회의
  • 채팅

요구사항 및 제한사항

SIP 애플리케이션 개발을 위한 요구사항은 다음과 같습니다.

  • Android 2.3 이상을 실행하는 휴대기기가 있어야 합니다.
  • SIP는 무선 데이터 연결을 통해 실행되므로 기기에 데이터 연결(모바일 데이터 서비스 또는 Wi-Fi 사용)이 있어야 합니다. 즉, AVD에서는 테스트할 수 없으며 실제 기기에서만 테스트할 수 있습니다. 자세한 내용은 SIP 애플리케이션 테스트를 참조하세요.
  • 애플리케이션 통신 세션의 각 참여자는 SIP 계정이 있어야 합니다. SIP 계정을 제공하는 여러 다양한 SIP 제공업체가 있습니다.

SIP API 클래스 및 인터페이스

다음은 Android SIP API에 포함된 클래스 및 하나의 인터페이스(SipRegistrationListener)에 관한 요약입니다.

클래스/인터페이스 설명
SipAudioCall SIP를 통한 인터넷 음성 통화를 처리합니다.
SipAudioCall.Listener 전화를 수신 중일 때('벨 울림 시') 또는 전화가 발신 중일 때('통화 시')와 같은 SIP 통화와 관련된 이벤트의 리스너입니다.
SipErrorCode SIP 작업 중에 반환되는 오류 코드를 정의합니다.
SipManager SIP 연결 시작과 같은 SIP 작업을 위한 API를 제공하고 관련 SIP 서비스의 액세스를 제공합니다.
SipProfile SIP 계정, 도메인 및 서버 정보를 포함한 SIP 프로필을 정의합니다.
SipProfile.Builder SipProfile을 생성하기 위한 도우미 클래스입니다.
SipSession SIP 대화상자와 관련되거나 대화상자 내에 있지 않은 독립형 트랜잭션과 관련된 SIP 세션을 나타냅니다.
SipSession.Listener 세션을 등록 중일 때('등록 시') 또는 전화가 발신 중일 때('통화 시')와 같은 SIP 세션과 관련된 이벤트의 리스너입니다.
SipSession.State '등록 중', '전화 발신 중' 및 '통화 중'과 같은 SIP 세션 상태를 정의합니다.
SipRegistrationListener SIP 등록 이벤트의 리스너인 인터페이스입니다.

manifest 생성

SIP API를 사용하는 애플리케이션을 개발하고 있다면 이 기능이 Android 2.3(API 레벨 9) 이상 버전의 플랫폼에서만 지원된다는 점에 유의하세요. 또한 Android 2.3(API 레벨 9) 이상을 실행하는 기기 중에서도 일부 기기만 SIP를 지원합니다.

SIP를 사용하려면 애플리케이션의 manifest에 다음 권한을 추가하세요.

  • android.permission.USE_SIP
  • android.permission.INTERNET

SIP를 지원할 수 있는 기기에만 애플리케이션이 설치될 수 있도록 하려면 애플리케이션의 manifest에 다음을 추가하세요.

<uses-sdk android:minSdkVersion="9" />

이는 애플리케이션에 Android 2.3 이상이 필요함을 나타냅니다. 자세한 내용은 API 레벨<uses-sdk> 요소에 관한 문서를 참조하세요.

SIP를 지원하지 않는 기기에서(예를 들어 Google Play에서) 애플리케이션이 필터링되는 방식을 제어하려면 애플리케이션의 manifest에 다음을 추가하세요.

<uses-feature android:name="android.hardware.sip.voip" />

이는 애플리케이션이 SIP API를 사용함을 명시한 것입니다. 선언에는 애플리케이션이 SIP를 지원하지 않는 기기에서 필터링되어야 하는지 여부를 나타내는 android:required 속성을 포함해야 합니다. 또한 구현에 따라 다른 <uses-feature> 선언이 필요할 수도 있습니다. 자세한 내용은 <uses-feature> 요소에 관한 문서를 참조하세요.

애플리케이션이 전화를 받도록 설계되었다면 다음과 같이 애플리케이션의 manifest에서 수신자(BroadcastReceiver 서브클래스)도 정의해야 합니다.

<receiver android:name=".IncomingCallReceiver" android:label="Call Receiver" />

다음은 SipDemo manifest에서 발췌한 내용입니다.

<?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
              package="com.example.android.sip">
      ...
         <receiver android:name=".IncomingCallReceiver" android:label="Call Receiver" />
      ...
      <uses-sdk android:minSdkVersion="9" />
      <uses-permission android:name="android.permission.USE_SIP" />
      <uses-permission android:name="android.permission.INTERNET" />
      ...
      <uses-feature android:name="android.hardware.sip.voip" android:required="true" />
      <uses-feature android:name="android.hardware.wifi" android:required="true" />
      <uses-feature android:name="android.hardware.microphone" android:required="true" />
    </manifest>
    

SipManager 생성

SIP API를 사용하려면 애플리케이션에서 SipManager 객체를 생성해야 합니다. SipManager는 애플리케이션에서 다음 사항을 처리합니다.

  • SIP 세션 시작
  • 전화 시작 및 수신
  • SIP 제공업체 등록 및 등록 취소
  • 세션 연결 확인

다음과 같이 새 SipManager를 인스턴스화합니다.

Kotlin

    val sipManager: SipManager? by lazy(LazyThreadSafetyMode.NONE) {
        SipManager.newInstance(this)
    }
    

자바

    public SipManager sipManager = null;
    ...
    if (sipManager == null) {
        sipManager = SipManager.newInstance(this);
    }
    

SIP 서버에 등록

일반적인 Android SIP 애플리케이션에는 각기 SIP 계정을 보유하는 사용자가 한 명 이상 포함됩니다. Android SIP 애플리케이션에서 각 SIP 계정은 SipProfile 객체로 표시됩니다.

SipProfile은 도메인 및 서버 정보와 SIP 계정을 포함한 SIP 프로필을 정의합니다. 애플리케이션을 실행하는 기기의 SIP 계정과 연결된 프로필을 로컬 프로필이라고 합니다. 세션이 연결된 프로필을 피어 프로필이라고 합니다. SIP 애플리케이션이 로컬 SipProfile을 사용하여 SIP 서버에 로그인하면 SIP 통화를 발신할 위치로 기기가 SIP 주소에 효과적으로 등록됩니다.

이 섹션에서는 SipProfile을 생성하여 SIP 서버에 등록하고 등록 이벤트를 추적하는 방법을 보여줍니다.

다음과 같이 SipProfile 객체를 만듭니다.

Kotlin

    private var sipProfile: SipProfile? = null
    ...

    val builder = SipProfile.Builder(username, domain)
            .setPassword(password)
    sipProfile = builder.build()
    

자바

    public SipProfile sipProfile = null;
    ...

    SipProfile.Builder builder = new SipProfile.Builder(username, domain);
    builder.setPassword(password);
    sipProfile = builder.build();
    

다음 코드 발췌문은 전화 걸기 또는 일반 SIP 전화 받기를 위한 로컬 프로필을 엽니다. 발신자는 mSipManager.makeAudioCall을 통해 후속 전화를 걸 수 있습니다. 또한 이 발췌문은 android.SipDemo.INCOMING_CALL 작업도 설정합니다. 이 작업은 기기가 전화를 받을 때 인텐트 필터에 의해 사용됩니다(인텐트 필터를 설정하여 전화 수신 참조). 등록 단계는 다음과 같습니다.

Kotlin

    val intent = Intent("android.SipDemo.INCOMING_CALL")
    val pendingIntent: PendingIntent = PendingIntent.getBroadcast(this, 0, intent, Intent.FILL_IN_DATA)
    sipManager?.open(sipProfile, pendingIntent, null)
    

자바

    Intent intent = new Intent();
    intent.setAction("android.SipDemo.INCOMING_CALL");
    PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, Intent.FILL_IN_DATA);
    sipManager.open(sipProfile, pendingIntent, null);
    

마지막으로 이 코드는 SipManagerSipRegistrationListener를 설정합니다. 이는 SipProfile이 SIP 서비스 제공업체에 성공적으로 등록되었는지 추적합니다.

Kotlin

    sipManager?.setRegistrationListener(sipProfile?.uriString, object : SipRegistrationListener {

        override fun onRegistering(localProfileUri: String) {
            updateStatus("Registering with SIP Server...")
        }

        override fun onRegistrationDone(localProfileUri: String, expiryTime: Long) {
            updateStatus("Ready")
        }

        override fun onRegistrationFailed(
                localProfileUri: String,
                errorCode: Int,
                errorMessage: String
        ) {
            updateStatus("Registration failed. Please check settings.")
        }
    })
    

자바

    sipManager.setRegistrationListener(sipProfile.getUriString(), new SipRegistrationListener() {

        public void onRegistering(String localProfileUri) {
            updateStatus("Registering with SIP Server...");
        }

        public void onRegistrationDone(String localProfileUri, long expiryTime) {
            updateStatus("Ready");
        }

        public void onRegistrationFailed(String localProfileUri, int errorCode,
            String errorMessage) {
            updateStatus("Registration failed.  Please check settings.");
        }
    }
    

애플리케이션이 프로필을 사용한 작업을 완료하면 프로필을 닫아 연결된 객체의 메모리 사용을 해제하고 서버에서 기기의 등록을 취소해야 합니다. 예를 들면 다음과 같습니다.

Kotlin

    fun closeLocalProfile() {
        try {
            sipManager?.close(sipProfile?.uriString)
        } catch (ee: Exception) {
            Log.d("WalkieTalkieActivity/onDestroy", "Failed to close local profile.", ee)
        }
    }
    

자바

    public void closeLocalProfile() {
        if (sipManager == null) {
           return;
        }
        try {
           if (sipProfile != null) {
              sipManager.close(sipProfile.getUriString());
           }
         } catch (Exception ee) {
           Log.d("WalkieTalkieActivity/onDestroy", "Failed to close local profile.", ee);
         }
    }
    

음성 통화하기

음성 통화를 하려면 다음 사항이 준비되어야 합니다.

  • 전화를 거는 SipProfile('로컬 프로필') 및 전화를 받을 유효한 SIP 주소('피어 프로필')
  • SipManager 객체

음성 통화를 하려면 SipAudioCall.Listener를 설정해야 합니다. 클라이언트가 SIP 스택과 상호작용하는 대부분은 리스너를 통해 이루어집니다. 이 스니펫에서는 통화가 설정된 후 SipAudioCall.Listener 설정 방법을 보여줍니다.

Kotlin

    var listener: SipAudioCall.Listener = object : SipAudioCall.Listener() {

        override fun onCallEstablished(call: SipAudioCall) {
            call.apply {
                startAudio()
                setSpeakerMode(true)
                toggleMute()
            }
        }

        override fun onCallEnded(call: SipAudioCall) {
            // Do something.
        }
    }
    

자바

    SipAudioCall.Listener listener = new SipAudioCall.Listener() {

       @Override
       public void onCallEstablished(SipAudioCall call) {
          call.startAudio();
          call.setSpeakerMode(true);
          call.toggleMute();
             ...
       }

       @Override

       public void onCallEnded(SipAudioCall call) {
          // Do something.
       }
    };
    

SipAudioCall.Listener를 설정했다면 전화를 걸 수 있습니다. SipManager 메서드 makeAudioCall에는 다음 매개변수가 사용됩니다.

  • 로컬 SIP 프로필(발신자)
  • 피어 SIP 프로필(통화가 연결될 사용자)
  • SipAudioCall의 통화 이벤트를 수신 대기할 SipAudioCall.Listener - 이 매개변수 값은 null일 수 있지만, 위에 나와 있는 것처럼 통화가 설정된 후 리스너를 사용하여 항목을 설정합니다.
  • 시간 제한 값(초)

예를 들면 다음과 같습니다.

Kotlin

    val call: SipAudioCall? = sipManager?.makeAudioCall(
            sipProfile?.uriString,
            sipAddress,
            listener,
            30
    )
    

자바

    call = sipManager.makeAudioCall(sipProfile.getUriString(), sipAddress, listener, 30);
    

전화 받기

전화를 받으려면 수신 전화가 있음을 나타내는 인텐트에 응답할 수 있는 BroadcastReceiver의 서브클래스가 SIP 애플리케이션에 포함되어 있어야 합니다. 따라서 애플리케이션에서 다음 작업을 해야 합니다.

  • AndroidManifest.xml에서 <receiver>를 선언합니다. SipDemo에서 이는 <receiver android:name=".IncomingCallReceiver" android:label="Call Receiver" />입니다.
  • BroadcastReceiver의 서브클래스인 수신자를 구현합니다. SipDemo에서 이는 IncomingCallReceiver입니다.
  • 누군가 로컬 프로필을 사용하여 전화를 걸 때 수신자를 실행하는 대기 중인 인텐트로 로컬 프로필(SipProfile)을 초기화합니다.
  • 수신 전화를 나타내는 작업을 기준으로 필터링하는 인텐트 필터를 설정합니다. SipDemo에서 이 작업은 android.SipDemo.INCOMING_CALL입니다.

BroadcastReceiver 서브클래스 생성

전화를 받으려면 SIP 애플리케이션에서 BroadcastReceiver 서브클래스를 생성해야 합니다. Android 시스템은 수신 SIP 통화를 처리하고 전화를 받을 때 '수신 전화' 인텐트를 브로드캐스트합니다(애플리케이션에서 정의한 대로). 다음은 SipDemo 샘플에서 생성된 BroadcastReceiver 서브클래스 코드입니다.

Kotlin

    /**
     * Listens for incoming SIP calls, intercepts and hands them off to WalkieTalkieActivity.
     */
    class IncomingCallReceiver : BroadcastReceiver() {

        /**
         * Processes the incoming call, answers it, and hands it over to the
         * WalkieTalkieActivity.
         * @param context The context under which the receiver is running.
         * @param intent The intent being received.
         */
        override fun onReceive(context: Context, intent: Intent) {
            val wtActivity = context as WalkieTalkieActivity

            var incomingCall: SipAudioCall? = null
            try {
                incomingCall = wtActivity.sipManager?.takeAudioCall(intent, listener)
                incomingCall?.apply {
                    answerCall(30)
                    startAudio()
                    setSpeakerMode(true)
                    if (isMuted) {
                        toggleMute()
                    }
                    wtActivity.call = this
                    wtActivity.updateStatus(this)
                }
            } catch (e: Exception) {
                incomingCall?.close()
            }
        }

        private val listener = object : SipAudioCall.Listener() {

            override fun onRinging(call: SipAudioCall, caller: SipProfile) {
                try {
                    call.answerCall(30)
                } catch (e: Exception) {
                    e.printStackTrace()
                }
            }
        }
    }
    

자바

    /**
     * Listens for incoming SIP calls, intercepts and hands them off to WalkieTalkieActivity.
     */
    public class IncomingCallReceiver extends BroadcastReceiver {
        /**
         * Processes the incoming call, answers it, and hands it over to the
         * WalkieTalkieActivity.
         * @param context The context under which the receiver is running.
         * @param intent The intent being received.
         */
        @Override
        public void onReceive(Context context, Intent intent) {
            SipAudioCall incomingCall = null;
            try {
                SipAudioCall.Listener listener = new SipAudioCall.Listener() {
                    @Override
                    public void onRinging(SipAudioCall call, SipProfile caller) {
                        try {
                            call.answerCall(30);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                };
                WalkieTalkieActivity wtActivity = (WalkieTalkieActivity) context;
                incomingCall = wtActivity.sipManager.takeAudioCall(intent, listener);
                incomingCall.answerCall(30);
                incomingCall.startAudio();
                incomingCall.setSpeakerMode(true);
                if(incomingCall.isMuted()) {
                    incomingCall.toggleMute();
                }
                wtActivity.call = incomingCall;
                wtActivity.updateStatus(incomingCall);
            } catch (Exception e) {
                if (incomingCall != null) {
                    incomingCall.close();
                }
            }
        }
    }
    

인텐트 필터를 설정하여 전화 수신

SIP 서비스는 새 전화를 받을 때 애플리케이션에서 제공한 작업 문자열로 인텐트를 보냅니다. SipDemo에서 이 작업 문자열은 android.SipDemo.INCOMING_CALL입니다.

SipDemo에서 발췌한 이 코드는 SipProfile 객체가 작업 문자열 android.SipDemo.INCOMING_CALL을 기반으로 대기 중인 인텐트를 통해 어떻게 생성되는지 보여줍니다. PendingIntent 객체는 SipProfile이 전화를 받을 때 브로드캐스트를 실행합니다.

Kotlin

    val sipManager: SipManager? by lazy(LazyThreadSafetyMode.NONE) {
        SipManager.newInstance(this)
    }

    var sipProfile: SipProfile? = null
    ...

    val intent = Intent("android.SipDemo.INCOMING_CALL")
    val pendingIntent: PendingIntent = PendingIntent.getBroadcast(this, 0, intent, Intent.FILL_IN_DATA)
    sipManager?.open (sipProfile, pendingIntent, null)
    

자바

    public SipManager sipManager = null;
    public SipProfile sipProfile = null;
    ...

    Intent intent = new Intent();
    intent.setAction("android.SipDemo.INCOMING_CALL");
    PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, Intent.FILL_IN_DATA);
    sipManager.open(sipProfile, pendingIntent, null);
    

인텐트 필터는 브로드캐스트를 가로채며, 이에 따라 수신자(IncomingCallReceiver)가 실행됩니다. 인텐트 필터를 애플리케이션의 manifest 파일에서 지정하거나 다음 SipDemo 샘플에 있는 애플리케이션 ActivityonCreate() 메서드에서와 같이 코드로 지정할 수 있습니다.

Kotlin

    class WalkieTalkieActivity : Activity(), View.OnTouchListener {
        ...
        lateinit var callReceiver: IncomingCallReceiver
        ...

        override fun onCreate(savedInstanceState: Bundle) {
            val filter = IntentFilter().apply {
                addAction("android.SipDemo.INCOMING_CALL")
            }
            callReceiver = IncomingCallReceiver()
            this.registerReceiver(callReceiver, filter)
            ...
        }
        ...
    }
    

자바

    public class WalkieTalkieActivity extends Activity implements View.OnTouchListener {
    ...
        public IncomingCallReceiver callReceiver;
        ...

        @Override
        public void onCreate(Bundle savedInstanceState) {

           IntentFilter filter = new IntentFilter();
           filter.addAction("android.SipDemo.INCOMING_CALL");
           callReceiver = new IncomingCallReceiver();
           this.registerReceiver(callReceiver, filter);
           ...
        }
        ...
    }
    

SIP 애플리케이션 테스트

SIP 애플리케이션을 테스트하려면 다음이 필요합니다.

  • Android 2.3 이상을 실행하는 휴대기기 - SIP는 무선으로 실행되므로 실제 기기에서 테스트해야 합니다. AVD 테스트는 작동하지 않습니다.
  • SIP 계정 - SIP 계정을 제공하는 여러 다양한 SIP 제공업체가 있습니다.
  • 전화를 걸려면 유효한 SIP 계정이 있어야 합니다.

SIP 애플리케이션을 테스트하려면 다음 단계를 따르세요.

  1. 기기에서 무선에 연결합니다(설정 > 무선 및 네트워크 > Wi-Fi > Wi-Fi 설정).
  2. 기기에서 개발에 설명된 대로 테스트할 휴대기기를 설정합니다.
  3. 기기에서 개발에 설명된 대로 휴대기기에서 애플리케이션을 실행합니다.
  4. Android 스튜디오를 사용한다면 Event Log 콘솔(View > Tool Windows > Event Log)을 열어 애플리케이션 로그 출력을 확인할 수 있습니다.
  5. 다음과 같이 애플리케이션이 실행될 때 Logcat을 자동으로 시작하도록 구성되어 있는지 확인합니다.
    1. Run > Edit Configurations를 선택합니다.
    2. Run/Debug Configurations 창에서 Miscellaneous 탭을 선택합니다.
    3. Logcat에서 Show logcat automatically, OK를 차례로 선택합니다.