Notice
Recent Posts
Recent Comments
Link
투케이2K
989. (Android/Java) [간단 소스] 안드로이드 Raw 폴더에 저장 된 MKV 형식 파일 Aws Kvs HLS 비디오 스트리밍 업로드 수행 본문
Android
989. (Android/Java) [간단 소스] 안드로이드 Raw 폴더에 저장 된 MKV 형식 파일 Aws Kvs HLS 비디오 스트리밍 업로드 수행
투케이2K 2025. 5. 22. 19:01[개발 환경 설정]
개발 툴 : AndroidStudio
개발 언어 : Java / Kotlin

[소스 코드]
// --------------------------------------------------------------------------------------
[개발 및 테스트 환경]
// --------------------------------------------------------------------------------------
- 언어 : Java / Kotlin
- 개발 툴 : AndroidStudio
- 기술 구분 : Aws / Kvs / Kinesis Video Streams
// --------------------------------------------------------------------------------------
// --------------------------------------------------------------------------------------
[사전) 필요 설정 정리] : build.gradle 의존성 부여
// --------------------------------------------------------------------------------------
// [AWS] : [target 31 이상 의존성]
implementation 'com.amazonaws:aws-android-sdk-kms:2.57.0'
implementation 'com.amazonaws:aws-android-sdk-s3:2.57.0'
implementation 'com.amazonaws:aws-android-sdk-iot:2.57.0'
implementation 'com.amazonaws:aws-android-sdk-mobile-client:2.57.0'
implementation 'com.amazonaws:aws-android-sdk-kinesisvideo:2.57.0'
// --------------------------------------------------------------------------------------
// --------------------------------------------------------------------------------------
[소스 코드]
// --------------------------------------------------------------------------------------
// -----------------------------------------------------------------------------------------
// TODO [SEARCH FAST] : [디바이스] : KVS HLS 스트리밍 업로드 수행
// -----------------------------------------------------------------------------------------
// TODO 필요 라이브러리 :
/*
implementation("com.squareup.okhttp3:okhttp:4.9.0")
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0'
// */
// -----------------------------------------------------------------------------------------
// TODO 필요 준비 : MKV 동영상 파일 준비 후 안드로이드 프로젝트 RAW 폴더에 추가 필요
// -----------------------------------------------------------------------------------------
// TODO 테스트 방법 : 해당 소스 코드 호출 이후 > Aws 콘솔에서 미디어 재생으로 확인
// -----------------------------------------------------------------------------------------
// [호출 소스 코드]
// -----------------------------------------------------------------------------------------
/*
try {
C_Aws_Kvs_Module.deviceKvsHlsStreamSend(A_Intro.this, C_Aws_Kvs_Module.ACCESS_KEY, C_Aws_Kvs_Module.SECRET_KEY, C_Aws_Kvs_Module.REGION, "DEVICE_1", "sample_640_360")
.subscribeOn(AndroidSchedulers.mainThread()) // [Observable (생성자) 로직을 IO 스레드에서 실행 : 백그라운드]
.observeOn(Schedulers.io()) // [Observer (관찰자) 로직을 메인 스레드에서 실행]
.subscribe(new Observer<Boolean>() { // [Observable.create 타입 지정]
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
public void onNext(@NonNull Boolean value) {
S_Log._W_("AWS KVS HLS 스트리밍 업로드 수행 :: onNext", new String[]{String.valueOf(value)});
}
@Override
public void onError(@NonNull Throwable e) {
S_Log._E_("AWS KVS HLS 스트리밍 업로드 수행 :: onError", new String[]{String.valueOf(e.getMessage())});
}
@Override
public void onComplete() {
}
});
}
catch (Exception e) {
S_Log._printStackTrace_(null, S_FinalData.LOG_BUG_STATE, null, e);
}
*/
// -----------------------------------------------------------------------------------------
public static String deviceKvsHlsStreamSend_Error_Message = "";
public static Observable<Boolean> deviceKvsHlsStreamSend(Context mContext, String ACCESS_KEY, String SECRET_KEY, String REGION, String STREAM_NAME, String RAW_MKV_NAME) {
S_Log._D_(ACTIVITY_NAME + " :: deviceKvsHlsStreamSend :: [디바이스] :: KVS HLS 스트리밍 업로드 수행", new String[]{
"ACCESS_KEY :: " + String.valueOf(ACCESS_KEY),
"SECRET_KEY :: " + String.valueOf(SECRET_KEY),
"REGION :: " + String.valueOf(REGION),
"STREAM_NAME :: " + String.valueOf(STREAM_NAME),
"RAW_MKV_NAME :: " + String.valueOf(RAW_MKV_NAME),
});
// [로직 처리 실시]
return Observable.create(subscriber -> {
try {
// [에러 메시지 초기화]
deviceKvsHlsStreamSend_Error_Message = "";
// [인풋 데이터 널 체크 수행]
if (mContext != null
&& C_Util.stringNotNull(ACCESS_KEY) == true
&& C_Util.stringNotNull(SECRET_KEY) == true
&& C_Util.stringNotNull(REGION) == true
&& C_Util.stringNotNull(STREAM_NAME) == true
&& C_Util.stringNotNull(RAW_MKV_NAME) == true){
// --------------------------------------------------------
// TODO [RAW 폴더에 저장 된 MKV 형식 동영상 파일 byte 읽음]
// --------------------------------------------------------
byte[] rawMkvByte = C_App.rawMkvReadByte((Activity) mContext, RAW_MKV_NAME);
if (rawMkvByte == null || rawMkvByte.length <= 0){
S_Log._E_(ACTIVITY_NAME + " :: deviceKvsHlsStreamSend :: [디바이스] :: KVS HLS 스트리밍 업로드 수행 실패", new String[]{"Raw Folder MKV File Is Null"});
// [에러 메시지 삽입]
deviceKvsHlsStreamSend_Error_Message = C_Format.form_message(new String[]{
"CODE :: " + C_Util.getSourceCodeLine(),
"MESSAGE :: " + "Raw 폴더에 저장 된 MKV 동영상 파일 존재를 다시 확인해주세요.",
"ERROR :: Raw Folder MKV File Is Null"
});
if (subscriber != null && subscriber.isDisposed() == false){
subscriber.onError(new Throwable(deviceCameraStreamRecord_Error_Message));
subscriber.onComplete();
}
return;
}
// --------------------------------------------------------
// TODO [HTTP 요청 처리 Thread 정의]
// --------------------------------------------------------
new Thread(){
@Override
public void run(){
// --------------------------------------------------------
// TODO [AWS 자격 증명 설정]
// --------------------------------------------------------
AWSCredentials credentials = new BasicAWSCredentials(ACCESS_KEY, SECRET_KEY);
// --------------------------------------------------------
// TODO [AWSKinesisVideoClient 생성]
// --------------------------------------------------------
AWSKinesisVideo awsKinesisVideoClient = new AWSKinesisVideoClient(credentials);
if (C_Util.stringNotNull(REGION) == true){ // TODO [리젼 정보 설정]
//awsKinesisVideoClient.setRegion(Region.getRegion("ap-northeast-1"));
awsKinesisVideoClient.setRegion(Region.getRegion(REGION));
}
S_Log._D_(ACTIVITY_NAME + " :: deviceKvsHlsStreamSend :: [디바이스] :: KVS HLS 스트리밍 업로드 데이터 엔트포인트 정보 요청 수행", new String[]{
"getAWSAccessKeyId :: " + String.valueOf(credentials.getAWSAccessKeyId()),
"getAWSSecretKey :: " + String.valueOf(credentials.getAWSSecretKey()),
});
// --------------------------------------------------------
// TODO [GetDataEndpointRequest 객체 생성 : KVS]
// --------------------------------------------------------
GetDataEndpointRequest dataEndPointRequest = new GetDataEndpointRequest()
.withStreamName(STREAM_NAME) // 스트림 이름 설정
.withAPIName(APIName.PUT_MEDIA); // TODO API 이름 설정 (PUT_MEDIA, GET_MEDIA 등)
// [로직 처리 수행]
try {
// --------------------------------------------------------
// TODO [GetDataEndpointResult 확인]
// --------------------------------------------------------
GetDataEndpointResult result = awsKinesisVideoClient.getDataEndpoint(dataEndPointRequest);
// [getDataEndpoint 출력]
String dataEndpoint = result.getDataEndpoint();
// [dataEndpoint 변수 체크]
if (C_Util.stringNotNull(dataEndpoint)){
S_Log._W_(ACTIVITY_NAME + " :: deviceKvsHlsStreamSend :: [디바이스] :: KVS HLS 스트리밍 업로드 데이터 엔트포인트 정보 확인 성공", new String[]{String.valueOf(dataEndpoint)});
// --------------------------------------------------------
// TODO [OkHttp RequestBody 생성]
// --------------------------------------------------------
// TODO 참고 : PutMedia 호출 이후 스트리밍 지속 호출 가능 한 세션이 맺어지고, 스트리밍 동영상을 일정 크기로 나눠서 지속 전송 수행
// --------------------------------------------------------
// TODO 참고 : PutMedia 호출은 지속적으로 계속 호출할 필요 없이 한번 호출 하면 지속 데이터 전송 가능한 세션이 맺어 집니다
// --------------------------------------------------------
// TODO 참고 : PutMedia 호출을 다시 하고 싶은 경우 DataEndpoint 호출 시 다시 호출 시도를 할 수 있습니다
// --------------------------------------------------------
RequestBody requestBody = new RequestBody() {
@Override
public MediaType contentType() {
return MediaType.parse("application/octet-stream");
}
@Override
public void writeTo(BufferedSink sink) throws IOException {
try (InputStream fis = new ByteArrayInputStream(rawMkvByte)) {
byte[] buffer = new byte[4096]; // TODO [4KB 단위로 스트리밍 데이터 전송]
//byte[] buffer = new byte[2048]; // TODO [2KB 단위로 스트리밍 데이터 전송]
int len;
while ((len = fis.read(buffer)) != -1) {
//S_Log._W_(ACTIVITY_NAME + " :: deviceKvsHlsStreamSend :: [디바이스] :: KVS HLS 스트리밍 데이터 Body Write 수행", new String[]{ String.valueOf(len) });
S_Log.w("KWON_TWOK", ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> KVS HLS 스트리밍 데이터 Body Write 수행 :: " + String.valueOf(len) + " >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
sink.write(buffer, 0, len);
sink.flush();
}
}
}
};
// --------------------------------------------------------
// TODO [AWS Signature V4 서명 적용]
// --------------------------------------------------------
// import com.amazonaws.DefaultRequest;
// import com.amazonaws.Request;
// --------------------------------------------------------
// TODO timecode-type (타임 코드 타입) : 프레임이 Kinesis 비디오 스트림에 입력될 때 타임스탬프가 생성 될 타입
// --------------------------------------------------------
// TODO ABSOLUTE (페이로드 타임 코드 절대적) / RELATIVE (페이로드 타임 코드 상대적)
// --------------------------------------------------------
// import okhttp3.Request;
// import okhttp3.RequestBody;
// import okhttp3.Response;
// import okio.BufferedSink;
// --------------------------------------------------------
S_Log._D_(ACTIVITY_NAME + " :: deviceKvsHlsStreamSend :: [디바이스] :: KVS HLS 스트리밍 AWS Signature V4 헤더 서명 수행 실시", null);
Request<?> signRequest = new DefaultRequest<>("kinesisvideo");
signRequest.setHttpMethod(HttpMethodName.POST);
signRequest.setEndpoint(new URI(dataEndpoint)); // TODO [DataEndpoint 적용]
signRequest.setEncodedResourcePath("/putMedia"); // TODO [Path 설정 : 해당 값 추가 중요]
signRequest.addHeader("Host", new URI(dataEndpoint).getHost()); // TODO [호스트 지정]
signRequest.addHeader("x-amzn-stream-name", STREAM_NAME); // TODO [스트림 명칭]
signRequest.addHeader("x-amzn-fragment-timecode-type", "ABSOLUTE"); // TODO [타임 코드]
// TODO [수동 설정 시 참고 코드]
/*
SimpleDateFormat dateTimeFormat = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'");
dateTimeFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
String amzDate = dateTimeFormat.format(new Date());
signRequest.addHeader("x-amzn-producer-start-timestamp", amzDate);
// */
signRequest.addHeader("Content-Type", "application/octet-stream"); // TODO [Body 에서 지정한 MediaType 타입]
signRequest.setContent(new StringInputStream("")); // TODO [Empty Body 지정 필요]
// --------------------------------------------------------
AWS4Signer signer = new AWS4Signer();
signer.setServiceName("kinesisvideo");
signer.setRegionName(REGION);
signer.sign(signRequest, credentials); // TODO [Sign 인증 수행]
// --------------------------------------------------------
// --------------------------------------------------------
// TODO [서명된 헤더들을 okhttp 헤더에 반영]
// --------------------------------------------------------
Headers.Builder okHttpHeaders = new Headers.Builder();
for (Map.Entry<String, String> entry : signRequest.getHeaders().entrySet()) {
okHttpHeaders.add(entry.getKey(), entry.getValue());
S_Log.w("KWON_TWOK", ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Okhttp Header Insert :: " + String.valueOf(entry.getKey()) + " :: " + String.valueOf(entry.getValue()) + " >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
}
// --------------------------------------------------------
// TODO [okhttp Request 빌더 생성]
// --------------------------------------------------------
String putMediaUrl = dataEndpoint + "/putMedia";
okhttp3.Request request = new okhttp3.Request.Builder()
.url(putMediaUrl) // TODO [최종 요청 URL]
.headers(okHttpHeaders.build()) // TODO [Signature V4 인증 헤더 값 추가]
.post(requestBody) // TODO [실시간 스트리밍 데이터 업로드 Body 추가 : writeTo]
.build();
// --------------------------------------------------------
// TODO [OkHttp 클라이언트 설정 (HTTP/2 지원)]
// --------------------------------------------------------
OkHttpClient client = new OkHttpClient.Builder()
.protocols(Arrays.asList(Protocol.HTTP_2, Protocol.HTTP_1_1))
.retryOnConnectionFailure(true)
.build();
// --------------------------------------------------------
// TODO [로그 출력]
// --------------------------------------------------------
S_Log._D_(ACTIVITY_NAME + " :: deviceKvsHlsStreamSend :: [디바이스] :: KVS HLS 스트리밍 업로드 HTTP 요청 수행", new String[]{
"mkv file total size :: " + String.valueOf(rawMkvByte.length),
"method :: " + String.valueOf(request.method()),
"url :: " + String.valueOf(request.url().toString()),
"headers :: " + String.valueOf(request.headers().toString()),
});
// --------------------------------------------------------
// TODO [HTTP 요청 및 응답 확인]
// --------------------------------------------------------
//try (Response response = client.newCall(request).execute()) {
try (Response response = client.newCall(request).execute()) {
String responseHeaders = response.headers().toString();
int responseCode = response.code();
String responseData = new String(response.body().bytes(), "UTF-8");
if (responseCode >= 200 && responseCode < 300){ // TODO [정상 응답]
S_Log._W_(ACTIVITY_NAME + " :: deviceKvsHlsStreamSend :: [디바이스] :: KVS HLS 스트리밍 업로드 HTTP 응답 확인", new String[]{
"responseHeaders :: " + String.valueOf(responseHeaders),
"responseCode :: " + String.valueOf(responseCode),
"responseData :: " + String.valueOf(responseData),
});
if (subscriber != null && subscriber.isDisposed() == false){
subscriber.onNext(true);
subscriber.onComplete();
}
}
else { // TODO [에러 응답]
S_Log._E_(ACTIVITY_NAME + " :: deviceKvsHlsStreamSend :: [디바이스] :: KVS HLS 스트리밍 업로드 HTTP 요청 에러", new String[]{
"responseHeaders :: " + String.valueOf(responseHeaders),
"responseCode :: " + String.valueOf(responseCode),
"responseData :: " + String.valueOf(responseData),
});
// [에러 메시지 삽입]
deviceKvsHlsStreamSend_Error_Message = C_Format.form_message(new String[]{
"CODE :: " + C_Util.getSourceCodeLine(),
"MESSAGE :: " + "[디바이스] :: KVS HLS 스트리밍 업로드 HTTP 응답 정보 확인이 필요합니다.",
"RESPONSE_HEADERS :: " + String.valueOf(responseHeaders),
"RESPONSE_CODE :: " + String.valueOf(responseCode),
"RESPONSE_DATA :: " + String.valueOf(responseData),
});
if (subscriber != null && subscriber.isDisposed() == false){
subscriber.onError(new Throwable(deviceKvsHlsStreamSend_Error_Message));
subscriber.onComplete();
}
}
} catch (IOException e) {
e.printStackTrace();
S_Log._D_(ACTIVITY_NAME + " :: deviceKvsHlsStreamSend :: [디바이스] :: KVS HLS 스트리밍 업로드 HTTP 요청 실패", new String[]{
"IOException :: " + String.valueOf(e.getMessage()),
});
// [에러 메시지 삽입]
deviceKvsHlsStreamSend_Error_Message = C_Format.form_message(new String[]{
"CODE :: " + C_Util.getSourceCodeLine(),
"MESSAGE :: " + "[디바이스] :: KVS HLS 스트리밍 업로드 HTTP 요청 중 문제가 발생했습니다.",
"EXCEPTION :: " + String.valueOf(e.getMessage())
});
if (subscriber != null && subscriber.isDisposed() == false){
subscriber.onError(new Throwable(deviceKvsHlsStreamSend_Error_Message));
subscriber.onComplete();
}
}
}
else {
S_Log._E_(ACTIVITY_NAME + " :: deviceKvsHlsStreamSend :: [디바이스] :: KVS HLS 스트리밍 업로드 HTTP 요청 실패", new String[]{ "Error :: getDataEndpoint Is Null" });
// [에러 메시지 삽입]
deviceKvsHlsStreamSend_Error_Message = C_Format.form_message(new String[]{
"CODE :: " + C_Util.getSourceCodeLine(),
"MESSAGE :: " + "[디바이스] :: KVS HLS 스트리밍 업로드 HTTP 요청 중 문제가 발생했습니다.",
"Error :: getDataEndpoint Is Null"
});
if (subscriber != null && subscriber.isDisposed() == false){
subscriber.onError(new Throwable(deviceKvsHlsStreamSend_Error_Message));
subscriber.onComplete();
}
}
} catch (Exception es) {
es.printStackTrace();
S_Log._E_(ACTIVITY_NAME + " :: deviceKvsHlsStreamSend :: [디바이스] :: KVS HLS 스트리밍 업로드 HTTP 요청 실패", new String[]{ "Exception :: " + String.valueOf(es.getMessage()) });
// [에러 메시지 삽입]
deviceKvsHlsStreamSend_Error_Message = C_Format.form_message(new String[]{
"CODE :: " + C_Util.getSourceCodeLine(),
"MESSAGE :: " + "[디바이스] :: KVS HLS 스트리밍 업로드 HTTP 요청 중 문제가 발생했습니다.",
"EXCEPTION :: " + String.valueOf(es.getMessage())
});
if (subscriber != null && subscriber.isDisposed() == false){
subscriber.onError(new Throwable(deviceKvsHlsStreamSend_Error_Message));
subscriber.onComplete();
}
}
}
}.start();
}
else {
S_Log._E_(ACTIVITY_NAME + " :: deviceKvsHlsStreamSend :: [디바이스] :: KVS HLS 스트리밍 업로드 에러", new String[]{"Input Data Is Null (ACCESS_KEY, SECRET_KEY, REGION, STREAM_NAME, RAW_MKV_NAME)"});
// [에러 메시지 삽입]
deviceKvsHlsStreamSend_Error_Message = C_Format.form_message(new String[]{
"CODE :: " + C_Util.getSourceCodeLine(),
"EXPLANATION :: " + "[디바이스] :: KVS HLS 스트리밍 업로드에 필요한 필수 값을 확인해주세요.",
"ERROR :: Input Data Is Null (ACCESS_KEY, SECRET_KEY, REGION, STREAM_NAME, RAW_MKV_NAME)"
});
if (subscriber != null && subscriber.isDisposed() == false){
subscriber.onError(new Throwable(deviceKvsHlsStreamSend_Error_Message));
subscriber.onComplete();
}
}
}
catch (Exception e){
S_Log._printStackTrace_(null, S_FinalData.LOG_BUG_STATE, null, e);
// [에러 메시지 삽입]
deviceKvsHlsStreamSend_Error_Message = C_Format.form_message(new String[]{
"CODE :: " + C_Util.getSourceCodeLine(),
"EXPLANATION :: " + "[디바이스] :: KVS HLS 스트리밍 업로드 중 예외 상황이 발생했습니다.",
"EXCEPTION [2] :: " + String.valueOf(e.getMessage())
});
if (subscriber != null && subscriber.isDisposed() == false){
subscriber.onError(new Throwable(deviceKvsHlsStreamSend_Error_Message));
subscriber.onComplete();
}
}
});
}
// --------------------------------------------------------------------------------------
// --------------------------------------------------------------------------------------
[참고 사이트]
// --------------------------------------------------------------------------------------
[kvs (Kinesis Video Streams) , HLS (HTTP 라이브 스트리밍) 설명 및 정리]
https://blog.naver.com/kkh0977/223724719256?trackingCode=blog_bloghome_searchlist
[AWS 사이트 주요 질문 정리 사이트]
https://aws.amazon.com/ko/kinesis/video-streams/faqs/
[Aws Kvs 실시간 영상 업로드 위한 엔드포인트 정보 확인 - GetDataEndpointRequest]
https://blog.naver.com/kkh0977/223855177383
// --------------------------------------------------------------------------------------
반응형
'Android' 카테고리의 다른 글
Comments