Notice
Recent Posts
Recent Comments
Link
투케이2K
183. (TWOK/WORK) [업무 이슈] 안드로이드 AWS WebRTC WebSocket Offer 전송 시 Answer 응답을 받지 못하는 이슈 - Base64 인코딩 필요 본문
투케이2K 업무정리
183. (TWOK/WORK) [업무 이슈] 안드로이드 AWS WebRTC WebSocket Offer 전송 시 Answer 응답을 받지 못하는 이슈 - Base64 인코딩 필요
투케이2K 2025. 10. 13. 19:22728x90
반응형
[제 목]
[업무 이슈] 안드로이드 AWS WebRTC WebSocket Offer 전송 시 Answer 응답을 받지 못하는 이슈 - Base64 인코딩 필요
[내 용]
------------------------------------------------------------------------------
[개발 및 테스트 환경]
------------------------------------------------------------------------------
- 제 목 : [업무 이슈] 안드로이드 AWS WebRTC WebSocket Offer 전송 시 Answer 응답을 받지 못하는 이슈 - Base64 인코딩 필요
- 테스트 환경 : Mobile / Samsung / Android / AWS / WebRTC / Viewer
- 사전) AWS KVS WebRTC 설명 :
>> WebRTC 란 웹, 애플리케이션, 디바이스 간 중간자 없이 오디오나 영상 미디어를 포착하고 실시간 스트림할 뿐 아니라, 임의의 데이터도 교환할 수 있도록 하는 기술입니다
>> WebRTC 는 간단한 API 를 통해 웹 브라우저, 모바일 애플리케이션 및 커넥티드 디바이스 간에 실시간 통신을 활성화할 수 있습니다
>> WebRTC 주요 용어 :
- SDP (Session Description Protocol) : 오디오/비디오 코덱, 해상도, 포트 등 스트리밍 정보를 담은 텍스트 포맷
- Offer / Answer : 통신 연결을 협상하기 위한 SDP 메시지 (초기 연결 설정)
- ICE (Interactive Connectivity Establishment) : NAT/P2P 환경에서도 연결 가능한 경로(IP, 포트 등)를 찾기 위한 기술
- Candidate : 가능한 연결 경로 (IP + Port 조합)
>> WebRTC [ICE] 연결 형태 :
- Relayed Address : TURN 서버가 패킷 릴레이를 위해 할당하는 주소
- Server Reflexive Address : NAT 가 매핑한 클라이언트의 공인망 (Public IP, Port)
- Local Address : 클라이언트의 사설주소 (Private IP, Port)
>> WebRTC STUN 및 TURN 서버 설명 :
- (같은 와이파이 망) STUN 서버는 HOST 를 거쳐 >> Server Reflexive Address 만을 응답하지만,
(릴레이서버 사용) TURN 서버는 Relayed Address와 Server Reflexive Address 를 모두 응답한다
- STUN, TURN 서버를 이용해 SDP Answer IP주소 를 취득 >> RTCPeerConnection Remote 연결 수행
>> WebRTC SDP 오퍼 생성 (뷰어) 및 응답 (마스터) 스트리밍 플로우 :
[Viewer → Signaling Server] -- SDP Offer --> [Master] : 뷰어는 마스터로 스트리밍 오퍼 신호 보낸다
[Master] -- SDP Answer --> [Viewer] : 마스터는 특정 뷰어의 오퍼 신호 확인 후 응답을 보낸다
[Viewer] -- ICE Candidate --> [Master] : 스트리밍을 할 수 있는 경로 확인
[Master] -- ICE Candidate --> [Viewer] : 스트리밍을 할 수 있는 경로 확인
P2P 연결 성립 → 스트리밍 시작
------------------------------------------------------------------------------
------------------------------------------------------------------------------
[이슈 사항]
------------------------------------------------------------------------------
1. 사전) 안드로이드에서 사용한 라이브러리 종류 : google-webrtc-1.0.30039.aar
>> https://artifactory.appodeal.com/appodeal-public/org/webrtc/google-webrtc/1.0.30039/
>> https://mvnrepository.com/artifact/org.webrtc/google-webrtc/1.0.32006
2. 안드로이드에서 WebRTC 뷰어 생성 후 SDP 메시지 교환을 위해 Offer 전송을 했지만, Answer 응답을 받지 못하고 무한 대기 상태 표시 이슈
------------------------------------------------------------------------------
------------------------------------------------------------------------------
[원인 파악 및 증상 재현]
------------------------------------------------------------------------------
1. 안드로이드에서 WebRTC 뷰어 영상 재생 화면 접속 수행
2. 안드로이드 EglBase.create 생성 및 PeerConnectionFactory 초기화 수행
3. 안드로이드 WebRTC Signaling Endpoint 조회 수행 - IceServer 조회 및 WebSocket 연결 시 사용
SingleMasterChannelEndpointConfiguration endpointConfig = new SingleMasterChannelEndpointConfiguration()
.withProtocols("WSS", "HTTPS")
.withRole("VIEWER");
4. 조회 된 HTTPS 주소를 사용해 ice-server 조회 AWS4Signer 헤더 생성 및 okhttp 를 사용해 ice 서버 리스트 조회 수행
// 요청 URL 설정
String iceUrl = httpEndpoint;
String icePath = "/v1/get-ice-server-config";
iceUrl += icePath;
// 요청 ServiceName
String iceServiceName = "kinesisvideo";
// AWS Request 객체 생성
com.amazonaws.Request<?> aws_ice_sign_request = new DefaultRequest<Void>(iceServiceName);
aws_ice_sign_request.setHttpMethod(HttpMethodName.POST);
aws_ice_sign_request.setEndpoint(URI.create(httpEndpoint));
aws_ice_sign_request.setEncodedResourcePath(icePath);
aws_ice_sign_request.addHeader("Content-Type", "application/json");
aws_ice_sign_request.setContent(new ByteArrayInputStream(iceBody.getBytes(StandardCharsets.UTF_8)));
// 서명자 설정
BasicAWSCredentials aws_ice_creds = new BasicAWSCredentials(ACCESS_KEY, SECRET_KEY);
AWS4Signer aws_ice_signer = new AWS4Signer();
aws_ice_signer.setServiceName(iceServiceName);
aws_ice_signer.setRegionName(REGION);
aws_ice_signer.sign(aws_ice_sign_request, aws_ice_creds);
5. 응답 받은 ice 서버 리스트 데이터 파싱 및 List<PeerConnection.IceServer> 에 추가 실시
6. 조회 된 WSS 주소를 사용해 WeSocket 웹소켓 연결 AWS4Signer 헤더 생성 및 presignedUrl 생성 실시
// 요청 ServiceName
String iceServiceName = "kinesisvideo";
// AWS Request 객체 생성
com.amazonaws.Request<?> socket_sign_request = new DefaultRequest<Void>(iceServiceName);
socket_sign_request.setHttpMethod(HttpMethodName.GET);
socket_sign_request.setEndpoint(URI.create(wssEndpoint)); // TODO Set URL
socket_sign_request.setEncodedResourcePath("/");
// Query Parameter 추가
Map<String, String> queryParams = new HashMap<>();
queryParams.put("X-Amz-ChannelARN", CHANNEL_ARN); // Set Channel Arn
queryParams.put("X-Amz-ClientId", CLIENT_ID); // Ser Viwer ClinetId
socket_sign_request.setParameters(queryParams);
// 서명자 설정
BasicAWSCredentials aws_socket_creds = new BasicAWSCredentials(ACCESS_KEY, SECRET_KEY);
AWS4Signer aws_socket_signer = new AWS4Signer();
aws_socket_signer.setServiceName(iceServiceName);
aws_socket_signer.setRegionName(REGION);
Date expiration = new java.util.Date(System.currentTimeMillis() + 60 * 1000); // 1분
aws_socket_signer.presignRequest(socket_sign_request, aws_socket_creds, expiration);
// TODO 실제 소켓 연결 Presigned URL 생성
String presignedUrl = "";
StringBuilder url = new StringBuilder(socket_sign_request.getEndpoint().toString());
url.append("?");
boolean first = true;
for (Map.Entry<String, String> entry : socket_sign_request.getParameters().entrySet()) {
if (!first) url.append("&");
first = false;
url.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8.name()))
.append("=")
.append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8.name()));
}
presignedUrl = url.toString();
7. peerConnection 객체 생성 수행 및 OkHttpClient 사용해 webSocket 접속 수행
8. webSocket onOpen 이 완료 된 경우 > peerConnection.createOffer 생성 수행
9 peerConnection offer 생성이 완료 된 경우 (onCreateSuccess) > webSocket Send Offer 메시지 전송 수행
>> Offer 전송 소스 코드 첨부 :
peerConnection.createOffer(new org.webrtc.SdpObserver() {
@Override
public void onCreateSuccess(SessionDescription sdp) {
S_Log._W_("STEP :: peerConnection :: createOffer :: onCreateSuccess", new String[]{ "type :: " + String.valueOf(sdp.type), "description :: " + String.valueOf(sdp.description) });
peerConnection.setLocalDescription(this, sdp); // Set Local Description
try {
JSONObject msg = new JSONObject();
msg.put("action", "SDP_OFFER"); // 기존 필드
msg.put("messageType", "SDP_OFFER"); // 추가 필드
msg.put("recipientClientId", ""); // 상대 clientId
msg.put("senderClientId", "TEST_VIEW_12345"); // 뷰어 clientId
JSONObject payload = new JSONObject();
payload.put("type", "offer");
payload.put("sdp", sdp.description);
msg.put("messagePayload", payload.toString());
webSocket.send(msg.toString()); // TODO Send WebSocket
S_Log._W_("STEP :: peerConnection :: createOffer :: Send WebSocket", new String[]{ "sdp.description :: " + String.valueOf(sdp.description), "send webSocket msg :: " + String.valueOf(msg) });
} catch (JSONException e) {
e.printStackTrace();
}
}
@Override public void onSetSuccess() {
S_Log._W_("STEP :: peerConnection :: createOffer :: onSetSuccess", null);
}
@Override public void onCreateFailure(String s) {
S_Log._E_("STEP :: peerConnection :: createOffer :: onCreateFailure", new String[]{ String.valueOf(s) });
}
@Override public void onSetFailure(String s) {
S_Log._E_("STEP :: peerConnection :: createOffer :: onSetFailure", new String[] { String.valueOf(s) });
}
}, constraints);
10. WebSocket onMessage 실시간 메시지 감지 부분에서 마스터로부터 Answer 응답이 내려 오지 않는 것 확인
------------------------------------------------------------------------------
------------------------------------------------------------------------------
[조치 내용]
------------------------------------------------------------------------------
1. 일반적인 WebRTC 는 Offer 메시지 전송 시 Json 평문을 사용해도 되지만, AWS KVS WebRTC Offer 전송 시에는 messagePayload 부분 value 를 Base64 로 인코딩해서 전송하도록 코드 수정
2. 수정 된 소스 코드 :
try {
JSONObject msg = new JSONObject();
msg.put("action", "SDP_OFFER"); // 기존 필드
msg.put("messageType", "SDP_OFFER"); // 추가 필드
msg.put("recipientClientId", ""); // 상대 clientId
msg.put("senderClientId", "TEST_VIEW_12345"); // 뷰어 clientId
JSONObject payload = new JSONObject();
payload.put("type", "offer");
payload.put("sdp", sdp.description);
msg.put("messagePayload", Base64.encodeToString(payload.toString().getBytes(), Base64.NO_WRAP));
webSocket.send(msg.toString()); // TODO Send WebSocket
S_Log._W_("STEP :: peerConnection :: createOffer :: Send WebSocket", new String[]{ "sdp.description :: " + String.valueOf(sdp.description), "send webSocket msg :: " + String.valueOf(msg) });
} catch (JSONException e) {
e.printStackTrace();
}
------------------------------------------------------------------------------
------------------------------------------------------------------------------
[참고 사이트]
------------------------------------------------------------------------------
[자바스크립트 AWS WebRTC 실시간 동영상 재생 수행]
https://blog.naver.com/kkh0977/223170500993?trackingCode=blog_bloghome_searchlist
[Aws Kvs WebRTC 실시간 영상 재생 관련 구성 요소 및 용어 정리]
https://blog.naver.com/kkh0977/223858189791
[업무 이슈] AWS WebRTC 실시간 비디오 재생 시 Client 클라이언트 연결 접속 및 해제 상태 확인 이슈
https://blog.naver.com/kkh0977/223966952222
[Aws Kinesis Video Streams] WebRTC SDP 협상 과정 프로세스 정리 정리
https://blog.naver.com/kkh0977/224030054470
[Aws Kinesis Video Streams] WebRTC getSignalingChannelEndpoint HTTPS, WSS 사용 범위 정리
https://blog.naver.com/kkh0977/224035890592
------------------------------------------------------------------------------
728x90
반응형
'투케이2K 업무정리' 카테고리의 다른 글
Comments
