Skip to content

Instantly share code, notes, and snippets.

@jinminghe
Created January 23, 2018 11:06
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jinminghe/f16d17ab9f41090640e7cb25962faf58 to your computer and use it in GitHub Desktop.
Save jinminghe/f16d17ab9f41090640e7cb25962faf58 to your computer and use it in GitHub Desktop.
AndroidTranscoder
import android.annotation.TargetApi;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.MediaMuxer;
import android.media.MediaMuxer.OutputFormat;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.SystemClock;
import android.view.Surface;
import java.io.IOException;
import java.nio.ByteBuffer;
@TargetApi(23)
public class SimpleTranscoder {
private static final String TAG = "SimpleTranscoder";
private long startTime;
private long endTime;
private MediaExtractor videoExtractor;
private MediaCodec videoDecoder;
private MediaCodec videoEncoder;
private MediaMuxer muxer;
private int outputVideoTrack = -1;
private HandlerThread videoDecodeThread;
private Handler videoDecodeThreadHandler;
private HandlerThread videoEncodeThread;
private Handler videoEncodeThreadHandler;
private boolean extractorDone = false;
public SimpleTranscoder() {
// Set up threads.
videoDecodeThread = new HandlerThread("videoDecodeThread");
videoDecodeThread.start();
videoDecodeThreadHandler = new Handler(videoDecodeThread.getLooper());
videoEncodeThread = new HandlerThread("videoEncodeThread");
videoEncodeThread.start();
videoEncodeThreadHandler = new Handler(videoEncodeThread.getLooper());
}
public void configure(String input, String output) throws IOException {
// Extractors
videoExtractor = new MediaExtractor();
videoExtractor.setDataSource(input);
int videoTrack = getAndSelectVideoTrackIndex(videoExtractor);
MediaFormat videoFormat = videoExtractor.getTrackFormat(videoTrack);
String mime = videoFormat.getString(MediaFormat.KEY_MIME);
// Codecs
videoDecoder = MediaCodec.createDecoderByType(mime);
videoEncoder = MediaCodec.createEncoderByType(mime);
videoDecoder.setCallback(new VideoDecodeCallBack(), videoDecodeThreadHandler);
videoEncoder.setCallback(new VideoEncodeCallBack(), videoEncodeThreadHandler);
videoEncoder.configure(createOutputFormat(), null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
Surface surface = videoEncoder.createInputSurface();
videoDecoder.configure(videoFormat, surface, null, 0);
// Muxer
muxer = new MediaMuxer(output, OutputFormat.MUXER_OUTPUT_MPEG_4);
}
public void start() {
videoEncoder.start();
videoDecoder.start();
}
public void release() {
}
private class VideoDecodeCallBack extends MediaCodec.Callback {
@Override
public void onInputBufferAvailable(MediaCodec codec, int index) {
ByteBuffer buffer = codec.getInputBuffer(index);
if (extractorDone) {
return;
}
int size = videoExtractor.readSampleData(buffer, 0);
long pts = videoExtractor.getSampleTime();
if (size >= 0) {
codec.queueInputBuffer(index, 0, size, pts, videoExtractor.getSampleFlags());
}
extractorDone = !videoExtractor.advance();
if (extractorDone) {
codec.queueInputBuffer(index, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
}
}
@Override
public void onOutputBufferAvailable(
MediaCodec codec, int index, MediaCodec.BufferInfo info) {
if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
codec.releaseOutputBuffer(index, false);
return;
}
boolean render = info.size != 0;
codec.releaseOutputBuffer(index, render);
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
videoEncoder.signalEndOfInputStream();
}
}
@Override
public void onError(
MediaCodec codec, MediaCodec.CodecException e) {
}
@Override
public void onOutputFormatChanged(
MediaCodec codec, MediaFormat format) {
}
}
private class VideoEncodeCallBack extends MediaCodec.Callback {
@Override
public void onInputBufferAvailable(MediaCodec codec, int index) {
}
@Override
public void onOutputBufferAvailable(
MediaCodec codec, int index, MediaCodec.BufferInfo info) {
ByteBuffer buffer = codec.getOutputBuffer(index);
if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
codec.releaseOutputBuffer(index, false);
return;
}
if (info.size != 0) {
muxer.writeSampleData(outputVideoTrack, buffer, info);
}
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
muxer.stop();
muxer.release();
}
codec.releaseOutputBuffer(index, false);
}
@Override
public void onError(
MediaCodec codec, MediaCodec.CodecException e) {
}
@Override
public void onOutputFormatChanged(
MediaCodec codec, MediaFormat format) {
outputVideoTrack = muxer.addTrack(format);
muxer.start();
}
}
private static MediaFormat createOutputFormat() {
MediaFormat result = MediaFormat.createVideoFormat("video/avc", 540, 960);
result.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
result.setInteger(MediaFormat.KEY_BIT_RATE, 1300000);
result.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
result.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
return result;
}
private static int getAndSelectVideoTrackIndex(MediaExtractor extractor) {
for (int i = 0; i < extractor.getTrackCount(); ++i) {
if (isVideoFormat(extractor.getTrackFormat(i))) {
extractor.selectTrack(i);
return i;
}
}
return -1;
}
private static int getAndSelectAudioTrackIndex(MediaExtractor extractor) {
for (int i = 0; i < extractor.getTrackCount(); ++i) {
if (isAudioFormat(extractor.getTrackFormat(i))) {
extractor.selectTrack(i);
return i;
}
}
return -1;
}
private static boolean isAudioFormat(MediaFormat format) {
return format.getString(MediaFormat.KEY_MIME).startsWith("audio/");
}
private static boolean isVideoFormat(MediaFormat format) {
return format.getString(MediaFormat.KEY_MIME).startsWith("video/");
}
}
@khordady
Copy link

where are threads?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment