android提供了一個強大的編解碼類MediaCodec,可以實作對視訊資料的編解碼,下面講一下如何對原始視訊資料寫死成h264格式的流
MediaCodec提供兩種方式的輸入,一種是将資料寫入它的輸入緩沖隊列裡,一種是讓MediaCodec建立一個輸入Surface,MediaCodec會自動從這個輸入Surface中讀取資料,因為我做的是錄制螢幕的需求,是以我是使用一個Surface輸入資料給MediaCodec。MediaCodec編碼出來的頭兩幀是特殊的,分别是sps 和 pps這兩幀在解碼時要用來配置解碼器用的。下面貼出編碼器代碼,這裡我做的是視訊實時編碼傳輸,是以編碼後的資料我使用socket發送的,大家主要還是看看編碼部分的代碼就好了:
package com.seewo.seewoair.coder;
/**
* @author zhangsutao
* @file VideoCodec.java
* @brief 視訊編解碼器基類
* @date 2016/8/7
*/
public interface VideoCodec {
String MIME_TYPE = "video/avc";
int VIDEO_FRAME_PER_SECOND = 15;
int VIDEO_I_FRAME_INTERVAL = 5;
int VIDEO_BITRATE = 500 * 8 * 1000;
}
/**
* @author zhangsutao
* @file VideoEncoder.java
* @brief 視訊編碼器
* @date 2016/7/29
*/
public class VideoEncoder implements VideoCodec {
private Worker mWorker;
private MediaProjection mMediaProjection;
private VirtualDisplay mVirtualDisplay;
private Client mClient;
//寫入本地的流,在調試的時候使用
private DataOutputStream mOutput;
private final boolean isDebug=true;
private final String TAG="VideoEncoder";
private byte[] mFrameByte;
public VideoEncoder(MediaProjection mediaProjection,Client client) {
mClient=client;
mMediaProjection=mediaProjection;
if(isDebug){
try {
mOutput=new DataOutputStream(new FileOutputStream(new File("/sdcard/h264encode")));;
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
protected void onSurfaceCreated(Surface surface, int mWidth, int mHeight) {
//将螢幕資料與surface進行關聯
mVirtualDisplay = mMediaProjection.createVirtualDisplay("-display",
mWidth, mHeight, 1, DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
surface, null, null);
}
protected void onSurfaceDestroyed(Surface surface) {
mVirtualDisplay.release();
surface.release();
}
public void start() {
if (mWorker == null) {
mWorker = new Worker();
mWorker.setRunning(true);
mWorker.start();
}
}
public void stop() {
if (mWorker != null) {
mWorker.setRunning(false);
mWorker = null;
}
if(mClient!=null){
if(!mClient.hasRelease()){
mClient.release();
}
}
}
private class Worker extends Thread {
private MediaCodec.BufferInfo mBufferInfo;
private MediaCodec mCodec;
private volatile boolean isRunning;
private Surface mSurface;
private final long mTimeoutUsec;
private int mWidth;
private int mHeight;
public Worker() {
mBufferInfo = new MediaCodec.BufferInfo();
mTimeoutUsec = 10000l;
}
public void setRunning(boolean running) {
isRunning = running;
}
protected void onEncodedSample(MediaCodec.BufferInfo info, ByteBuffer data) {
if(mFrameByte==null||mFrameByte.length<info.size){
mFrameByte=new byte[info.size];
}
data.get(mFrameByte,0,info.size);
boolean isSuccess1=mClient.sendInt(info.size);
boolean isSuccess2=mClient.send(mFrameByte,0,info.size);
Log.d(TAG,"sending success:"+isSuccess1+" "+isSuccess2);
if(!(isSuccess1&&isSuccess2)){
isRunning=false;
mClient.release();
}
//在debug時在本地寫一份
if(isDebug){
try {
mOutput.writeInt(info.size);
mOutput.write(mFrameByte,0,info.size);
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
if(!prepare()){
isRunning=false;
}
while (isRunning) {
encode();
}
release();
}
void encode() {
if (!isRunning) {
//編碼結束,發送結束信号,讓surface不在提供資料
mCodec.signalEndOfInputStream();
}
int status = mCodec.dequeueOutputBuffer(mBufferInfo, mTimeoutUsec);
if (status == MediaCodec.INFO_TRY_AGAIN_LATER) {
return;
} else
if (status >= 0) {
ByteBuffer data = mCodec.getOutputBuffer(status);
if (data != null) {
final int endOfStream = mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM;
//傳遞編碼資料
if (endOfStream == 0) {
onEncodedSample(mBufferInfo, data);
}
// 一定要記得釋放
mCodec.releaseOutputBuffer(status, false);
if (endOfStream == MediaCodec.BUFFER_FLAG_END_OF_STREAM){
return;
}
}
}
}
private void release() {
onSurfaceDestroyed(mSurface);
if(mCodec!=null){
mCodec.stop();
mCodec.release();
}
if(mOutput!=null){
try {
mOutput.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private boolean prepare() {
// configure video output
mWidth= SpUtils.readInt(Constans.KEY_DEVICE_WIDTH,-1);
mHeight=SpUtils.readInt(Constans.KEY_DEVICE_HEIGHT,-1);
if(mWidth==-1||mHeight==-1){
return false;
}
mClient.connectToServer();
//發送寬高
boolean isSuccess1=mClient.sendInt(mWidth);
boolean isSuccess2=mClient.sendInt(mHeight);
if(!(isSuccess1&&isSuccess2)){
isRunning=false;
mClient.release();
}
MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mWidth, mHeight);
format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
format.setInteger(MediaFormat.KEY_BIT_RATE, VIDEO_BITRATE);
format.setInteger(MediaFormat.KEY_FRAME_RATE, VIDEO_FRAME_PER_SECOND);
format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,VIDEO_I_FRAME_INTERVAL);
format.setInteger(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER,40);
try {
mCodec = MediaCodec.createEncoderByType(MIME_TYPE);
} catch (IOException e) {
e.printStackTrace();
return false;
}
mCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
//建立關聯的輸入surface
mSurface = mCodec.createInputSurface();
mCodec.start();
onSurfaceCreated(mSurface,mWidth,mHeight);
return true;
}
}
}