20년정도 개발일을 했다.
대부분 Core Layer 부분을 C나 어셈블리로 작업했다.
MediaPlayer의 Engine core 부분이나 다양한 플렛폼의 System porting 작업만 주로 했기에 UI에 대해선 생소하기만 했다.
회사에서 컨텐츠 encoding 작업할 경우 동영상 퀄리티가 떨어진다는 QA팀의 리포팅으로 여러 값을 마음대로 video encoder에 넣어서 테스트 할 수 있는 Test Tool이 필요하게 됐다.
우선, Android에서 작업하기로 하고 간단한 프로세스 루틴을 정했다.
-
Transcoding할 미디어 파일을 선택
-
Android MediaCodec에 Query를 날려 해당 컨텐츠의 정보를 얻어온다. MediaExtractor, MediaFormat 사용.
-
MediaCodec을 사용하여 decoder/encoder 생성.
-
encoder 생성시 사용자 입력을 받아 BitRate, FrameRate, Resolution, I-Frame Interval, QualityRange etc 등을 설정할 수 있게 한다.
-
Extractor -> decoder -> render -> encoder -> mux 작업이 실제 진행 루틴이다.
-
Rendering은 inputSurface, outputSurface를 만들고 최종 mp4 파일로 저장은 MediaMux를 사용하게 했다.
-
Resolution 변경은 openGL을 사용해야 하기 때문에 인터넷에 돌아 다니는 컨포넌트인 inputSurface/outputSurface class를 사용했다. 이 코드는 Android CTS code를 참조했다.
-
Transcoding을 완료하면 원본 미디어파일과 Transcoding된 미디어 파일을 동시에 재생해서 비교하도록 한다. VideoView class 사용.
기본적인 Transcoding하는 루틴은 기존에 하던 일이라 별로 어려움이 없었다. Android에서 지원하는 여러 Class들을 사용하여 정보를 얻고 다시 설정하여 Transcoding 루틴을 수행하는 Thread에 작업을 맡기면 되는 것이다.
하지만, UI 작업이 생소한 나에겐 Android의 UI layer를 구성하는 방법들과 각각의 컨포넌트를 사용하는 방법에 익숙해져야 했다.
아래는 최종화면이다.
Transcoding할 Media File을 선택해야 아래 버튼들과 설정 컨포넌트들이 Enable되게 해놨다.
우선 Transcoding을 하기 위해선 아래의 방법으로 Android에서 제공하는 Media class들을 사용한다.
1. MediaExtractor 생성
MediaExtractor extractor = MediaExtractor();
try {
extractor.setDataSource(mPath);
} catch (IOException e) {
e.printStackTrace();
}
mPath는 UI에서 MediaFile을 선택해서 가져 오도록 URI 정보를 파싱해서 얻는다. 파일 path는 아래의 방식으로 얻도록 했다.
btnSelectFile.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent( );
intent.setType("video/*");
intent.setAction(Intent.ACTION_PICK);
mActivity.startActivityForResult(intent, 100);
btnCompareVideoFile.setEnabled(false);
}
});
위 intent 처리의 결과로 아래와 같이 응답이 오게 된다. startActivityForResult() 함수에서 response code를 100으로 해줬다.
URI에서 파일의 실제 경로를 얻는 FileUtils은 인터넷에 돌아 다니는 class를 그냥 가져다 썼다.
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == Activity.RESULT_OK)
{
switch (requestCode) {
case 100:
Uri selectedMediaUri = data.getData();
//szPath = getRealPathFromURI(mContext, selectedMediaUri);
szPath = FileUtils.getPath(this, selectedMediaUri);
if (szPath != null) {
textPath.setText(szPath);
/*
* 파일 선택한 후 파일정보 및 encoder 옵션 설정하게 해주는 화면설정.
*/
setCapabilityScreen();
/*
* 단말에서 지원하는 mime type의 video encoder list를 위해서 spinner를 array 사용하게 해준다.
*/
spinnerAdapter = new ArrayAdapter(this, R.layout.support_simple_spinner_dropdown_item, encoderList);
spinnerEncoder.setAdapter(spinnerAdapter);
bSelectFile = true;
} else {
textPath.setText("Please select video file!");
}
default:
break;
}
}
}
2. Create Output format.
UI에서 입력받은 해상도 및 여러 옵션들을 설정하여 출력파일의 format을 만든다.
private static MediaFormat createOutputFormat(MediaFormat inputFormat) {
MediaFormat outFormat = MediaFormat.createVideoFormat(OUTPUT_VIDEO_MIME_TYPE, mWidth, mHeight);
if (outFormat == null) {
return null;
}
3. encoder / decoder 생성.
Media Transcoder의 동작은 원본 미디어 파일의 frame을 읽어와 decoding한 후 그 결과를 설정된 MediaFormat에 맞게 변환하여 encoder에 넣어주고 MediaMux에 file writing하는 구조이다.
decoding 및 encoding 작업을 위한 핸들을 만든다.
/*
* Encoder를 만든다.
* UI에서 선택한 encoder로 만든다.
*/
try {
encoder = MediaCodec.createByCodecName(szSelectedEncoderName);
} catch (IOException e) {
e.printStackTrace();
}
encoder.configure(outputFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
보통의 단말은 encoder/decoder가 몇개씩 존재한다. 제조사 및 Google에서 기본으로 지원하는 encoder/decoder가 있다.
보통 codec list를 구해서 codec list에서 먼저 나오는것을 선택하여 사용하지만, 여기서는 encoder마다 특성이 다르기 때문에 UI에서 선택하여 사용할 수 있도록 했다.
encoder list를 구하는 코드는 아래와 같다.
private static void getCodecList(String mimeType) {
int numCodecs = MediaCodecList.getCodecCount();
for (int i = 0; i < numCodecs; i++) {
MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
if (!codecInfo.isEncoder()) {
continue;
}
String[] types = codecInfo.getSupportedTypes();
for (int j = 0; j < types.length; j++) {
if (types[j].equalsIgnoreCase(mimeType)) {
encoderList.add(codecInfo.getName());
}
}
}
}
encoderList에 MediaCodecList로부터 구한 AVC(H.264) encoder를 넣어주고 나중에 UI에 출력 및 사용자가 선택할 수 있게 만들었다.
대부분의 구현 루틴이 Android의 developer page를 보면 자세히 나와있다. 아래 gitHub에 공유한 MediaTranscoder source코드에 자세한 코멘트를 달아 놨다.
UI 작업을 처음하는 입장에선 해당 Platform의 UI layer에 대한 동작 흐름과 프로토콜을 이해하고 작업을 해야 할것 같다.
작업 중, Transcoding 종료처리 작업에서 계속 core layer에서 crash가 발생했다. 디버깅 결과 android activity와 연결된 surface가 openGL 처리 과정에서 blocking이 될경우 발생되는 watchdog crash였다.
이런 UI와 관련된 히스토리를 모르면 삽질을 하게 되는것 같다. 회사에서 iOS 버전도 필요하다고 하여 Swift 및 AVKit을 사용하여 동일한 작업을하는 Transcoder를 만들고 있다.