이 글은 기존 운영했던 WordPress 블로그인 PyxisPub: Development Life (pyxispub.uzuki.live) 에서 가져온 글 입니다. 모든 글을 가져오지는 않으며, 작성 시점과 현재 시점에는 차이가 많이 존재합니다.
작성 시점: 2017-07-11
Transcoding 는 영화 파일, 오디오 파일, 문자 인코딩 등 하나에서 다른 하나로의 디지털 변환 작업을 의미한다. 보통 동영상에서는 동영상의 비트열을 변환하는 뜻으로 사용된다.
기존 안드로이드에서는 FFmpeg 등을 사용했었는데, 아래와 같은 문제가 있었다.
하지만 API 18 (4.3부터) FFmpeg 를 사용하지 않고도 Video Transcoding가 가능하게 되었다.
바로, MediaMuxer, MediaCodec, MediaExtractor 등이다.
그리고 이런 API 등을 쉽게 사용할 수 있게 만들어진 라이브러리가 android-transcoder 이다.
README에 잘 나왔긴 했지만 삽질하면서 얻은 기록을 이쪽에 정리하는 것이 좋을거라 생각해서 쓰게 되었다.
앱 단에서 신경써야 할 것은 어느정도 있다.
그래서 해당 라이브러리도 Callback 형태로 결과값을 넘겨주고 있다. 내 경우에는 영상 업로드 프로세스 전에 타야 되었기 때문에 어쩔 수 없이 UI Blocking를 해야 할 필요가 있었다.
그런고로 ProgressDialog 도입.
private FileDescriptor getFileDescriptor() {
File file = new File(incomePath);
try {
FileInputStream stream = new FileInputStream(file);
return stream.getFD();
} catch (IOException e) {
return null;
}
}
내 경우에는 업로드 전에 시행했기 때문에 로컬 경로를 가지고 있었고, 이에 바로 FileDescriptor 로 변환이 가능했다.
16:9 이외의 영상 (960x540, iOS가 보통 이걸로 인코딩 한다) 는 다른 안드로이드 기기에서 작동을 보증하지 않는다.
안드로이드 기기들은 CTS (Compatibility Test Suite) 를 통과해야 하는데, 이 CTS는 1280x720 해상도로 지원하는 미디어 포맷 문서 에 있는 포맷들을 테스트한다.
즉 이외 해상도는 지원해야 될 의무가 없으며 이는 하드웨어 코덱의 부재 및 제한으로 이어질 수 있다.
이를 위해서는 자르거나 합쳐서 16:9로 만들어야 하는데, 이를 아직 android-transcoder 에선 지원하지 않는다.
어떻게 보면, 이 문제로 2시간 이상 삽질했다.
즉, 결과 경로가 다른 파일로 저장되어야 한다. 만일 같은 파일로 할 경우에는 해상도만 변경된 채 비트레이트 등 정보가 바뀌지 않아 용량이 줄어들지 않는다.
나중에도 활용할 수 있도록 만든 클래스가 아래와 같다.
import android.app.Activity;
import android.app.ProgressDialog;
import android.graphics.Bitmap;
import android.media.MediaFormat;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import net.ypresto.androidtranscoder.MediaTranscoder;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* VideoTranscodingUtils
* Created by Pyxis on 2017-07-11.
*/
public class VideoTranscodingUtils implements MediaTranscoder.Listener {
private Activity activity;
private ProgressDialog progressDialog;
private String incomePath;
private String outcomePath;
private OnResultListener listener;
public static final int TRANSCODING_SUCCESS = 1;
public static final int TRANSCODING_FAILED = 2;
public VideoTranscodingUtils(Activity activity, String path) {
this.activity = activity;
this.incomePath = path;
File folder = new File(Environment.getExternalStorageDirectory(), "encoding_output/");
folder.mkdir();
try {
this.outcomePath = File.createTempFile("encoding", ".mp4", folder).getAbsolutePath();
} catch (IOException e) {
Log.d(VideoTranscodingUtils.class.getSimpleName(), "encoding failed");
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
progressDialog = new ProgressDialog(activity, android.R.style.Theme_Material_Light_Dialog);
} else {
progressDialog = new ProgressDialog(activity, android.R.style.Theme_Holo_Light_Dialog);
}
progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progressDialog.setMessage("Transcoding...");
progressDialog.setCancelable(false);
}
public void transcode(final OnResultListener listener) {
this.listener = listener;
FileDescriptor descriptor = getFileDescriptor();
MediaTranscoder.getInstance().transcodeVideo(descriptor, outcomePath,
MediaFormatStrategyPresets.createAndroid720pStrategy(8000 * 1000, 128 * 1000, 1), this);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
progressDialog.show();
}
});
}
private FileDescriptor getFileDescriptor() {
File file = new File(incomePath);
try {
FileInputStream stream = new FileInputStream(file);
return stream.getFD();
} catch (IOException e) {
return null;
}
}
private void dismissDialog() {
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
try {
if (activity.isDestroyed() && !progressDialog.isShowing())
return;
progressDialog.dismiss();
} catch (Exception e) {
}
}
});
}
@Override
public void onTranscodeProgress(final double progress) {
}
@Override
public void onTranscodeCompleted() {
dismissDialog();
listener.onResult(TRANSCODING_SUCCESS, outcomePath);
}
@Override
public void onTranscodeCanceled() {
dismissDialog();
listener.onResult(TRANSCODING_FAILED, incomePath);
}
@Override
public void onTranscodeFailed(Exception exception) {
dismissDialog();
listener.onResult(TRANSCODING_FAILED, incomePath);
}
public interface OnResultListener {
void onResult(int resultCode, String outPath);
}
}
비디오 비트레이트, 오디오 비트레이트, 오디오 채널을 변경하려면 MediaFormatStrategyPresets.createAndroid720pStrategy(8000 * 1000, 128 * 1000, 1)
의 파라미터 값을 조정하면 된다. 여기서는 비디오 비트레이트 8mbps, 오디오 128kbps, 1번 오디오 채널을 사용했고, 바꿀 해상도는 720p (1280x720) 이다.
깔끔하게 인코딩에 성공했다. 확실히 FFmpeg를 사용하는 것 보다는 위험 부담도 적고, 매우 쉽게 되어있었다.