前言
MediaCodec是Android提供的编解码音视频的一个类,是一个比较底层的类,使用起来比较麻烦,通常用于一些特定场景下,比如,播放自己加密的视频流、视频封装等,今天通过使用MediaCodec播放视频来简单的了解它。
几个方法
创建实例与配置
MediaCodec的构造函数是私有的,只能通过其提供的静态方法创建
//第一个 通过名称 通过MediaCodecList获取手机支持的
public static MediaCodec createByCodecName(@NonNull String name)
//第二个 通过视频类型创建解码器 如:video/avc
public static MediaCodec createDecoderByType(@NonNull String type)
//第三个 通过视频类型创建编码器 如:video/avc
public static MediaCodec createEncoderByType(@NonNull String type)
配置
//配置方法,MediaFormat:视频格式,Surface:关联Surface,
public void configure(
@Nullable MediaFormat format,
@Nullable Surface surface, @Nullable MediaCrypto crypto,
@ConfigureFlag int flags)
输入缓冲区相关的方法
获取空闲缓冲区
//获取空闲的缓冲区下标,timeoutUs:超时时间,-1:一直等待,0:立刻返回
public final int dequeueInputBuffer(long timeoutUs)
//获取缓冲区实例,index:对应上面的下标
public ByteBuffer getInputBuffer(int index)
//获取空闲的缓冲区数组,已过时,自SDK版本21废弃
public ByteBuffer[] getInputBuffers()
数据写入与入队
ByteBuffer .put(...)//将数据写入缓冲区
//空闲缓冲区入队 index:缓冲区下标,offset:偏移,size:数据大小,presentationTimeUs:时间戳,flag:标志位
public final void queueInputBuffer(
int index,
int offset, int size, long presentationTimeUs, int flags)
输出缓冲区相关的方法
获取输出缓冲区
//输出缓冲区出队,返回输出缓冲区下标,>0正常获取到处理后的数据,BufferInfo:数据信息,timeoutUs:超时时间,-1:一直等待,0:立刻返回
public final int dequeueOutputBuffer(
@NonNull BufferInfo info, long timeoutUs)
数据释放
//释放输出缓冲区数据,index:输出缓冲区下标,reder:是否渲染
public final void releaseOutputBuffer(int index, boolean render)
数据来源
因视频数据需要一帧一帧的处理与视频相关信息的获取,这里借助系统的另外一个类MediaExtractor来处理,
通过MediaExtractor可获取视频格式,视频每一帧的数据和时间戳。
实现
MainActivity
package com.zy.mediacodec;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.annotation.SuppressLint;
import android.media.AudioTrack;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import static android.media.MediaCodec.BUFFER_FLAG_CODEC_CONFIG;
import static android.media.MediaCodec.BUFFER_FLAG_END_OF_STREAM;
import static android.media.MediaCodec.BUFFER_FLAG_KEY_FRAME;
import static android.media.MediaCodec.CONFIGURE_FLAG_ENCODE;
public class MainActivity extends AppCompatActivity implements SurfaceHolder.Callback {
private final static String TAG = "MediaCodec";
private SurfaceView surface_view;
private DecodeThread mDecodeThread;
private File file;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
surface_view = findViewById(R.id.surface_view);
surface_view.getHolder().addCallback(this);
file = new File(getExternalCacheDir(), "test.mp4");
copyFile();
}
/**
* 复制文件
*/
private void copyFile() {
if (!file.exists()) {
try {
InputStream inputStream = getResources().getAssets().open("test.mp4");
int len = 0;
byte[] bytes = new byte[1024 * 1024];
FileOutputStream fileOutputStream = new FileOutputStream(file);
while ((len = inputStream.read(bytes)) != -1) {
fileOutputStream.write(bytes, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void onPlay(View view) {
if (mDecodeThread != null)
mDecodeThread.stopPlay();
mDecodeThread = new DecodeThread(surface_view.getHolder().getSurface(), file.getPath(), new Handler() {
@SuppressLint("HandlerLeak")
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1:
MediaFormat trackFormat = (MediaFormat) msg.obj;
int width = trackFormat.getInteger(MediaFormat.KEY_WIDTH);
int height = trackFormat.getInteger(MediaFormat.KEY_HEIGHT);
int h = (int) (surface_view.getWidth() * height * 1f / width);
ViewGroup.LayoutParams layoutParams = surface_view.getLayoutParams();
layoutParams.height = h;
surface_view.setLayoutParams(layoutParams);
break;
}
}
});
mDecodeThread.start();
}
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
}
}
布局XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="开始"
android:onClick="onPlay"/>
<SurfaceView
android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
解码线程
/**
* 视频解码类
*/
public class DecodeThread extends Thread {
private String path;
private MediaCodec mediaCodec;
private MediaExtractor mediaExtractor;
private Surface surface;
private Handler handler;
private boolean EFO;
private boolean pause;
public DecodeThread(Surface surface, String path, Handler handler) {
this.surface = surface;
this.path = path;
this.handler = handler;
EFO = false;
pause = false;
}
@Override
public void run() {
super.run();
try {
mediaExtractor = new MediaExtractor();
mediaExtractor.setDataSource(path);
int trackCount = mediaExtractor.getTrackCount();
for (int index = 0; index < trackCount; index++) {
MediaFormat trackFormat = mediaExtractor.getTrackFormat(index);
String mime = trackFormat.getString(MediaFormat.KEY_MIME);
if (mime.startsWith("video/")) {
mediaExtractor.selectTrack(index);
Message message = Message.obtain();
message.what = 1;
message.obj = trackFormat;
handler.sendMessage(message);
try {
mediaCodec = MediaCodec.createDecoderByType(mime);
} catch (IOException e) {
e.printStackTrace();
}
mediaCodec.configure(trackFormat, surface, null, 0);
}
}
mediaCodec.start();
long startMs = System.currentTimeMillis();
while (!isInterrupted()) {
if (EFO){
break;
}
//处理输入数据
int inputIndex = mediaCodec.dequeueInputBuffer(0);
if (inputIndex >= 0) {
ByteBuffer inputBuffer = mediaCodec.getInputBuffer(inputIndex);
int sampleData = mediaExtractor.readSampleData(inputBuffer, 0);
if (sampleData > 0) {//存在数据
mediaCodec.queueInputBuffer(inputIndex, 0, sampleData, mediaExtractor.getSampleTime(), 0);
mediaExtractor.advance();//下一帧
} else {//结束
mediaCodec.queueInputBuffer(inputIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
}
}
//处理输出数据
while (true) {
MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
int outputIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
if (outputIndex >= 0) {
//
while ((bufferInfo.presentationTimeUs / 1000) > (System.currentTimeMillis() - startMs)) {
Thread.sleep(10);
}
mediaCodec.releaseOutputBuffer(outputIndex, true);
} else {
break;
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (mediaExtractor != null)
mediaExtractor.release();
if (mediaCodec != null)
mediaCodec.release();
}
}
public void stopPlay() {
EFO = true;
}
}
结语
通过这次的学习,掌握MediaCodec编解码的基本流程和相关方法,本次只是实现了播放视频,还可以封装视频,如录像功能,将摄像头采集的视频信息封装为mp4格式的视频,之后会进一步尝试实现。