MediaCodec之H264编码

H264是一种很常见的视频编码方式。在做流媒体开发中,h264会经常遇到。由于之前对流媒体一无所知,在做项目时,绕了不少的弯。所幸,网上关于h264的资料很多,只要细细看,都能看懂。
Mediacodec相对MediaRecorder来说,比较偏底层一些,Mediacodec的API 解释说:MediaCodec类可以用于访问低级媒体编解码器。因此Mediacodec可以实现一些MediaRecorder不能实现的功能。

思路:创建SurfaceView,并且实现SurfaceHolder.Callback接口,该接口有三个抽象方法,常用的也就两个。在surfaceCreated方法中实例化相机,得到相机取得的视频数据,实例化Mediacodec并且开始编码。
surfaceDestroyed方法中,销毁相机,停止Mediacodec编码。
照相机要取得实时的数据,需要实现PreviewCallback接口,重写onPreviewFrame(byte[] data, android.hardware.Camera camera)()方法,这个方法里面的data参数,就是照相机输出的原始数据,我们编码的对象,就是它。

首先要实现下面的这两个接口,一个是SurfaceView必须要实现的接口,一个是相机输出数据的接口。

public class MainActivity extends Activity  implements SurfaceHolder.Callback,PreviewCallback

创建相机并且开启预览

    private void startcamera(Camera mCamera){
        if(mCamera != null){
            try {
                mCamera.setPreviewCallback(this);//
                mCamera.setDisplayOrientation(90);//旋转90
                if(parameters == null){
                    parameters = mCamera.getParameters();
                }
                List sizes = parameters.getSupportedPictureSizes();

                //parameters = mCamera.getParameters();
                parameters.setPreviewFormat(ImageFormat.NV21);//输出的格式,最好不要修改
                parameters.setPreviewSize(width, height);
                mCamera.setParameters(parameters);
                mCamera.setPreviewDisplay(surfaceHolder);
                mCamera.startPreview();//开启预览

            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

从摄像头获取数据:

   @Override
    public void onPreviewFrame(byte[] data, android.hardware.Camera camera) {
       //data就是摄像头输出的数据,Mediacodec编码的对象就是data
    }

实例化Mediacodec并且开始编码
此方法在摄像头刚打开时就调用,保证摄像头刚开启时,编码就已经开始。

   private void initMediaCodec() {
        bitrate = 2 * width * height * framerate ;//码率
        try {
            mMediaCodec = MediaCodec.createEncoderByType("video/avc");
            MediaFormat  mediaFormat = MediaFormat.createVideoFormat("video/avc", height, width); //height和width一般都是照相机的height和width。   
            //描述平均位速率(以位/秒为单位)的键。 关联的值是一个整数
            mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
            //描述视频格式的帧速率(以帧/秒为单位)的键。
            mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, framerate);//帧率,一般在15至30之内,太小容易造成视频卡顿。
            mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, 19);//色彩格式,具体查看相关API,不同设备支持的色彩格式不尽相同
            //关键帧间隔时间,单位是秒
            mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2);
            mMediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            mMediaCodec.start();//开始编码
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

对onPreviewFrame(byte[] data, Camera camera)中的data进行编码:

 byte[] input = data;
byte[] yuv420sp = new byte[width*height*3/2];
NV21ToNV12(input,yuv420sp,width,height);
input = yuv420sp;
          if (input != null) {
                        try {
                            ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();//拿到输入缓冲区,用于传送数据进行编码
                            ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();//拿到输出缓冲区,用于取到编码后的数据  
                            int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);
                            if (inputBufferIndex >= 0) {//当输入缓冲区有效时,就是>=0
                               ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
                                inputBuffer.clear();
                                inputBuffer.put(input);//往输入缓冲区写入数据,
                                //                    //五个参数,第一个是输入缓冲区的索引,第二个数据是输入缓冲区起始索引,第三个是放入的数据大小,第四个是时间戳,保证递增就是
                              mediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, System.nanoTime() / 1000, 0);

                            }

                            MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
                            int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);//拿到输出缓冲区的索引
                            while (outputBufferIndex >= 0) {
                                ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
                                byte[] outData = new byte[bufferInfo.size];
                                outputBuffer.get(outData);
                                //outData就是输出的h264数据
                                outputStream.write(outData, 0, outData.length);//将输出的h264数据保存为文件,用vlc就可以播放

                                mediaCodec.releaseOutputBuffer(outputBufferIndex, false);
                                outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);
                            }

                        } catch (Throwable t) {
                            t.printStackTrace();
                        }
                    }

创建文件夹

  private void createfile(){
        File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/test.h264");
        if(file.exists()){
            file.delete();
        }
        try {
            outputStream = new BufferedOutputStream(new FileOutputStream(file));
        } catch (Exception e){
            e.printStackTrace();
        }
    }

NV21格式转化为NV12格式

    private void NV21ToNV12(byte[] nv21,byte[] nv12,int width,int height){
        if(nv21 == null || nv12 == null)return;
        int framesize = width*height;
        int i = 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];
        }
    }

你可能感兴趣的:(其他)