MediaCodec 硬编码 h264

本篇文章记录一下,Android调用mediacodec编码camera回掉的YUV数据为h264的方法。

由于公司需要,软编码(X264)由于手机性能的瓶颈,已不能满足要求,所以决定使用硬编码。其实硬编码最早用过MediaRecord,但是不能直接得到h264数据,得先编成MP4,再从MP4里把H264的NALU取出来,感觉太绕了,所以当时抛弃了MediaRecord,选择了x264。不过看来,现在还得走上硬编码的路了  --  MediaCodec

这篇文章就用一个demo来说一下mediacodec的调用吧。

首先,要获取到CAMERA的回掉回来的YUV数据。

其次,将获得到的数据用MEDIACODEC编码为H264。

最后,将H264写入文件,程序结束后,可用VLC等支持播放H264的播放器查看效果。

先说下获取YUV数据吧,这个很简单了,直接上代码


packagecom.example.mediacodecencode;

importjava.io.IOException;

importjava.util.ArrayList;

importjava.util.concurrent.ArrayBlockingQueue;

importandroid.annotation.SuppressLint;

importandroid.annotation.TargetApi;

importandroid.app.Activity;

importandroid.graphics.ImageFormat;

importandroid.hardware.Camera;

importandroid.hardware.Camera.Parameters;

importandroid.hardware.Camera.PreviewCallback;

importandroid.media.MediaCodecInfo;

importandroid.media.MediaCodecList;

importandroid.os.Build;

importandroid.os.Bundle;

importandroid.util.Log;

importandroid.view.SurfaceHolder;

importandroid.view.SurfaceView;

publicclassMainActivityextendsActivityimplementsSurfaceHolder.Callback,PreviewCallback{

privateSurfaceView surfaceview;

privateSurfaceHolder surfaceHolder;

privateCamera camera;

privateParameters parameters;

intwidth =1280;

intheight =720;

intframerate =30;

intbiterate =8500*1000;

privatestaticintyuvqueuesize =10;

publicstaticArrayBlockingQueue

 YUVQueue =newArrayBlockingQueue

(yuvqueuesize);

privateAvcEncoder avcCodec;

@Override

protectedvoidonCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

surfaceview = (SurfaceView)findViewById(R.id.surfaceview);

surfaceHolder = surfaceview.getHolder();

surfaceHolder.addCallback(this);

SupportAvcCodec();

}

@Override

publicvoidsurfaceCreated(SurfaceHolder holder) {

camera = getBackCamera();

startcamera(camera);

avcCodec =newAvcEncoder(width,height,framerate,biterate);

avcCodec.StartEncoderThread();

}

@Override

publicvoidsurfaceChanged(SurfaceHolder holder,intformat,intwidth,intheight) {

}

@Override

publicvoidsurfaceDestroyed(SurfaceHolder holder) {

if(null!= camera) {

camera.setPreviewCallback(null);

camera.stopPreview();

camera.release();

camera =null;

avcCodec.StopThread();

}

}

@Override

publicvoidonPreviewFrame(byte[] data, android.hardware.Camera camera) {

// TODO Auto-generated method stub

putYUVData(data,data.length);

}

publicvoidputYUVData(byte[] buffer,intlength) {

if(YUVQueue.size() >=10) {

YUVQueue.poll();

}

YUVQueue.add(buffer);

}

@SuppressLint("NewApi")

privatebooleanSupportAvcCodec(){

if(Build.VERSION.SDK_INT>=18){

for(intj = MediaCodecList.getCodecCount() -1; j >=0; j--){

MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(j);

String[] types = codecInfo.getSupportedTypes();

for(inti =0; i < types.length; i++) {

if(types[i].equalsIgnoreCase("video/avc")) {

returntrue;

}

}

}

}

returnfalse;

}

privatevoidstartcamera(Camera mCamera){

if(mCamera !=null){

try{

mCamera.setPreviewCallback(this);

mCamera.setDisplayOrientation(90);

if(parameters ==null){

parameters = mCamera.getParameters();

}

parameters = mCamera.getParameters();

parameters.setPreviewFormat(ImageFormat.NV21);

parameters.setPreviewSize(width, height);

mCamera.setParameters(parameters);

mCamera.setPreviewDisplay(surfaceHolder);

mCamera.startPreview();

}catch(IOException e) {

e.printStackTrace();

}

}

}

@TargetApi(9)

privateCamera getBackCamera() {

Camera c =null;

try{

c = Camera.open(0);// attempt to get a Camera instance

}catch(Exception e) {

e.printStackTrace();

}

returnc;// returns null if camera is unavailable

}

}


其实没啥说的,很简答的逻辑。不过上面代码有这么几点可以说一下:

1.camera start的时机最好放在surfaceCreated,销毁最好放在surfaceDestroyed;

2.camera parameters setPreviewFormat的时候在5.0一下系统使用NV21或YV12,因为基本所有的安卓手机都支持这两种预览格式;

3.最好在程序的开始,判断一下系统是否支持MediaCodec编码h264,具体逻辑可见上面的SupportAvcCodec方法。

4.上面的代码中,可以看出,我把YUV数据放到一个队列里面了,准备使用。

其次就是使用MediaCodec编码h264了,首先,初始化MediaCodec,方法如下:


@SuppressLint("NewApi")

publicAvcEncoder(intwidth,intheight,intframerate,intbitrate) {

m_width  = width;

m_height = height;

m_framerate = framerate;

MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", width, height);

mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar);

mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, width*height*5);

mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE,30);

mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL,1);

try{

mediaCodec = MediaCodec.createEncoderByType("video/avc");

}catch(IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

mediaCodec.configure(mediaFormat,null,null, MediaCodec.CONFIGURE_FLAG_ENCODE);

mediaCodec.start();

createfile();

}


需要注意的一点是,对于比特率,其实完全可以这样处理,N*width*height,N可设置为1 2 3或者1 3 5等,来区分低/中/高的码率。

另外,我选择了YUV420SP作为编码的目标颜色空间,其实YUV420SP就是NV12,咱们CAMERA设置的是NV21,所以需要转一下。转换方法如下:


privatevoidNV21ToNV12(byte[] nv21,byte[] nv12,intwidth,intheight){

if(nv21 ==null|| nv12 ==null)return;

intframesize = width*height;

inti =0,j =0;

System.arraycopy(nv21,0, nv12,0, framesize);

for(i =0; i < framesize; i++){

nv12[i] = nv21[i];

}

for(j =0; j < framesize/2; j+=2)

{

nv12[framesize + j-1] = nv21[j+framesize];

}

for(j =0; j < framesize/2; j+=2)

{

nv12[framesize + j] = nv21[j+framesize-1];

}

}


下面,就是编码的函数了,我这里把编码放在一个线程里,去轮训YUV队列,如有有数据就编码,具体如下:


publicvoidStartEncoderThread(){

Thread EncoderThread =newThread(newRunnable() {

@SuppressLint("NewApi")

@Override

publicvoidrun() {

isRuning =true;

byte[] input =null;

longpts =0;

longgenerateIndex =0;

while(isRuning) {

if(MainActivity.YUVQueue.size() >0){

input = MainActivity.YUVQueue.poll();

byte[] yuv420sp =newbyte[m_width*m_height*3/2];

NV21ToNV12(input,yuv420sp,m_width,m_height);

input = yuv420sp;

}

if(input !=null) {

try{

longstartMs = System.currentTimeMillis();

ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();

ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();

intinputBufferIndex = mediaCodec.dequeueInputBuffer(-1);

if(inputBufferIndex >=0) {

pts = computePresentationTime(generateIndex);

ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];

inputBuffer.clear();

inputBuffer.put(input);

mediaCodec.queueInputBuffer(inputBufferIndex,0, input.length, pts,0);

generateIndex +=1;

}

MediaCodec.BufferInfo bufferInfo =newMediaCodec.BufferInfo();

intoutputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);

while(outputBufferIndex >=0) {

//Log.i("AvcEncoder", "Get H264 Buffer Success! flag = "+bufferInfo.flags+",pts = "+bufferInfo.presentationTimeUs+"");

ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];

byte[] outData =newbyte[bufferInfo.size];

outputBuffer.get(outData);

if(bufferInfo.flags ==2){

configbyte =newbyte[bufferInfo.size];

configbyte = outData;

}elseif(bufferInfo.flags ==1){

byte[] keyframe =newbyte[bufferInfo.size + configbyte.length];

System.arraycopy(configbyte,0, keyframe,0, configbyte.length);

System.arraycopy(outData,0, keyframe, configbyte.length, outData.length);

outputStream.write(keyframe,0, keyframe.length);

}else{

outputStream.write(outData,0, outData.length);

}

mediaCodec.releaseOutputBuffer(outputBufferIndex,false);

outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);

}

}catch(Throwable t) {

t.printStackTrace();

}

}else{

try{

Thread.sleep(500);

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}

}

});

EncoderThread.start();

}



需要注意的有两点,其实也是两个坑:

坑1:mediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, pts, 0); 第四个参数,是否需要传入?我觉得必须得传,因为不传的话,你就会发现mediaCodec.dequeueOutputBuffer变了第一个I帧之后,一直返回-1。

坑2:关于mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC)的超时时间是否要传,穿多少?我觉得不能传-1(不能丢帧,一直等),传-1会卡住,要么编码非常卡,传多少合适呢,传11000吧,下过不错。

下面贴一下计算PTS的方法:


/**

* Generates the presentation time for frame N, in microseconds.

*/

private long computePresentationTime(long frameIndex) {

return 132 + frameIndex * 1000000 / m_framerate;

}


这样,大概就说完了,其实也很简单,不过,就是编码的时候一些参数的设置非常重要,例如一款硬件比较差的设备,那么帧率就得设置的低一些,码率也一样。

如果发现编码出来之后,播放很卡,那么请降低帧率,降低码率。

在github上面穿了例子,地址如下:

https://github.com/sszhangpengfei/MediaCodecEncodeH264

原文地址:http://blog.csdn.net/ss182172633/article/details/50256733

你可能感兴趣的:(MediaCodec 硬编码 h264)