APP录制视频一般调用mediarecorder或者mediacodec接口。mediarecorder的优势就是简单。几行代码就可以搞定。相比于mediarecorder,mediaocdec的优势就是自由度更高,app可以加一些自己的前处理、后处理等算法。所以一般app都会选择mediacodec接口。
本文将介绍app调用mediacodec录像的调用过程:
实现方式一: 异步方式调用mediacodec,输入为非surface:
private MediaCodec mEncoder;
public init() {
mEncoder = MediaCodec.createEncoderByType(mime);
mEncoder.setCallback(new CustomCallback(), handler); // app设置了callback就表示用异步的方式,否则就是同步方式
mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); // 此处format就是码率,帧率,尺寸等信息
}
public void start() throws Exception {
mEncoder.start();
}
public void stop() {
mEncoder.stop();
}
主要看callback需要怎么实现
private class CustomCallback extends MediaCodec.Callback {
@Override
public void onInputBufferAvailable(@NonNull MediaCodec codec, int index) {
// codec有input buffer可用
ByteBuffer inputBuffer = mEncoder.getInputBuffer(index); // 取出可用的buffer
inputBuffer.clear(); // 清空buffer
inputBuffer.put(mEncodeBufferHolder.data); // 把需要处理的数据放进buffer
mEncoder.queueInputBuffer(index, 0, // 把buffer传给codec
mEncodeBufferHolder.data.length, mEncodeBufferHolder.presentationTimeUs, mEncodeBufferHolder.flag);
}
@Override
public void onOutputBufferAvailable(@NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo info) {
// codec有output buffer可用
ByteBuffer encodedData = mEncoder.getOutputBuffer(index); // 取出可用的buffer
mediaMuxer.writeSampleData(mTrackIndex, encodedData, info); // 把buffer写入文件
mEncoder.releaseOutputBuffer(index, false); // 把buffer传给codec
}
@Override
public void onError(@NonNull MediaCodec codec, @NonNull MediaCodec.CodecException e) {
// 收到codec有error的消息,此处会处理error
}
@Override
public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) {
// outputbuffer 格式有变化
}
}
实现方式二: 异步方式调用mediacodec,输入为surface:
private MediaCodec mEncoder;
private Surface inputSurface;
private EGLRender mEglRender;
private VirtualDisplay mVirtualDisplay;
public init() {
mEncoder = MediaCodec.createEncoderByType(mime);
mEncoder.setCallback(new CustomCallback(), handler); // app设置了callback就表示用异步的方式,否则就是同步方式
mEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); // 此处format就是码率,帧率,尺寸等信息
inputSurface = videoEncoder.createInputSurface(); // 从底层获取surface
// 把surface传给render
mEglRender = new EGLRender(mVideoEncoder.createInputSurface(), mWidth, mHeight, mFrameRate, offsetTime);
}
public void start() throws Exception {
mEglRender.setStartTimeNs(getSystemTimeNanosWithMode());
mEglRender.start();
mEncoder.start();
}
public void stop() {
mEglRender.stopRender();
mEncoder.stop();
}
private void prepareVirtualDisplay() {
// creates a VirtualDisplay to capture the contents of the screen
Surface surface = mEglRender.getDecodeSurface();
int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
mVirtualDisplay = mMediaProjection.createVirtualDisplay(TAG + "-display",
mWidth, mHeight, VIRTUAL_DISPLAY_DPI, flags,
surface, null, null);
LogUtil.i(TAG, "create virtual display success, virtual display = " + mVirtualDisplay);
}
看看callback的具体实现:
private class CustomCallback extends MediaCodec.Callback {
@Override
public void onOutputBufferAvailable(@NonNull MediaCodec codec, int index, @NonNull MediaCodec.BufferInfo info) {
// codec有output buffer可用
ByteBuffer encodedData = mEncoder.getOutputBuffer(index); // 取出可用的buffer
mediaMuxer.writeSampleData(mTrackIndex, encodedData, info); // 把buffer写入文件
mEncoder.releaseOutputBuffer(index, false); // 把buffer传给codec
}
@Override
public void onError(@NonNull MediaCodec codec, @NonNull MediaCodec.CodecException e) {
// 收到codec有error的消息,此处会处理error
}
@Override
public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) {
// outputbuffer 格式有变化
}
}
此处由于codec的输入为surface不需要app调用queue/dequeue去实现input buffer的回调,所以此处call back不会有onInputBufferAvailable。
tips:
上面的surface是app通过mediacodec createInputSurface获取,然后app把surface传给eglrender还有一种方式是app自己获取surface之后调用mediacodec的setInputSurface把surface传下去。代码简介如下:
encoder = MediaCodec.createByCodecName(codec);
encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
inputSurface = new InputSurface(MediaCodec.createPersistentInputSurface());
encoder.setInputSurface(inputSurface);
encoder.start();
emmm........此处其实也是从mediacodec先获取了surface在设置下去。。。。
实现方式三: 同步方式调用mediacodec,输入为surface:
private MediaCodec mVideoEncoder;
private VideoHandleThread mVideoThread;
private volatile AtomicBoolean mVideoStart = new AtomicBoolean(false);
public init() {
mVideoEncoder = MediaCodec.createEncoderByType(MIME_TYPE);
mVideoEncoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); // 此处format就是码率,帧率,尺寸等信息
mEglRender = new EGLRender(mVideoEncoder.createInputSurface(), mWidth, mHeight, mFrameRate, offsetTime);
mVideoThread = new VideoHandleThread();
}
public void start() throws Exception {
mVideoEncoder.start();
mVideoThread.start();
}
public void stop() {
mVideoEncoder.stop();
}
同步调用的时候会创建一个VideoHandleThread线程专门处理video的数据:
private class VideoHandleThread extends Thread {
@Override
public void run() {
while (mVideoStart.get()) {
int index = mVideoEncoder.dequeueOutputBuffer(mVideoBufferInfo, TIMEOUT_US);
if (index >= 0 && mMuxerStarted) {
encodeToVideoTrack(index);
mVideoEncoder.releaseOutputBuffer(index, false);
} else if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
Thread.sleep(10);
}
}
}
}
private void encodeToVideoTrack(int index) {
ByteBuffer encodedData = mVideoEncoder.getOutputBuffer(index);
mMuxer.writeSampleData(mVideoTrackIndex, encodedData, mVideoBufferInfo);
}
同步处理的时候没有callback,一个while循环就一直在处理,如果获取不到buffer就需要app自己做sleep操作。总之,同步的时候,mediacodec不会告知app啥时候去取buffer,app需要自己实现取出buffer的等待逻辑。
tips:
无论同步还是异步处理,针对video部分buffer的处理一定要开辟一个子线程处理。如果在app主线程处理,很容易出现死锁、block主线程、ANR等问题。