Android硬编、硬解h264

项目工程demo地址https://github.com/liluojun/PlayVideo

demo包含硬编解h264、libyuv裁剪图像、opengles渲染yuv数据、ffmpeg解码裸h264数据等功能,故仅供参考测试。

硬编码首先设置编码器

MediaFormat mediaFormat = MediaFormat.createVideoFormat("video/avc", width, height);
mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);//色彩空间
mediaFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);//码率
mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, m_framerate);//帧率
mediaFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2);//I帧间隔
try {
    mediaCodec = MediaCodec.createEncoderByType("video/avc");
} catch (Exception e) {
    e.printStackTrace();
}
mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
mediaCodec.start();//启动编码器

这块基本是模板代码,编码器常用的参数都在这里。

喂数据编码,数据来源手机相机

public void encoder(byte[] input) {
    if (isRuning) {
        try {
            if (input != null && input.length != 0) {
                if (colorFormat <= 20) {
                    JavaToNativeMethod.getInstence().nv21ToI420(input, yuv420sp, m_width, m_height, yByte, uByte, vByte);
                } else {
                    JavaToNativeMethod.getInstence().nv21ToNv12(input, yuv420sp, m_width, m_height, yByte, uByte, vByte);
                }
            }
            if (!encode) {
                return;
            }
            if (input != null && input.length != 0) {
                try {
                    ByteBuffer[] inputBuffers = mediaCodec.getInputBuffers();
                    ByteBuffer[] outputBuffers = mediaCodec.getOutputBuffers();
                    int inputBufferIndex = mediaCodec.dequeueInputBuffer(-1);
                    Log.e("index", "" + inputBufferIndex);
                    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;
                    } else {
                        return;
                    }
                    MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
                    int outputBufferIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, TIMEOUT_USEC);
                    while (outputBufferIndex >= 0) {
                        UiVideoData u = new UiVideoData();
                        ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
                        byte[] outData = new byte[bufferInfo.size];
                        outputBuffer.get(outData);
                    }
                    bufferInfo = null;
                } catch (Exception e) {
                    Log.e("Encoder", "编码错误" + e.getMessage());
                }
            }

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

转换数据格式我使用的是Android手机默认的nv21格式,但是受到手机存储yuv数据格式的限制故需要转换格式,格式转换用的是libyuv库,代码如下:

if (colorFormat <= 20) {
    JavaToNativeMethod.getInstence().nv21ToI420(input, yuv420sp, m_width, m_height, yByte, uByte, vByte);
} else {
    JavaToNativeMethod.getInstence().nv21ToNv12(input, yuv420sp, m_width, m_height, yByte, uByte, vByte);
}

编码基本上是模板代码在 outputBuffer.get(outData);这里outdata就是编码后的数据了。

关于sps与pps的获取:

if (spsPpsBuffer.getInt() == 0x00000001) {
    byte[] mMediaHead = new byte[outData.length];
    System.arraycopy(outData, 0, mMediaHead, 0, outData.length);
    configbyte = outData;
    mMediaHead = null;
    outData = null;
    break;
}

在编码器启动后出的第一个数据就是sps与pps信息。

关于I帧的判断:

(outData[4] & 0x1f) == 5

这里我采用的是上面的判定方式,网上也有其他的判定方式如outData[4]==65||outData[4]==25之类的。

强制I帧有时候我们会遇到需要强行出一帧I帧的情况,只需要调用编码器的flush()方法即可。

硬解码首先也是设置解码器

mediaformat = MediaFormat.createVideoFormat("video/avc", w, h);
mediaformat.setByteBuffer("csd-0", ByteBuffer.wrap(sps));//设置sps
mediaformat.setByteBuffer("csd-1", ByteBuffer.wrap(pps));//设置pps
mediaformat.setInteger(MediaFormat.KEY_FRAME_RATE, m_framerate);//帧率
mediaformat.setInteger(MediaFormat.KEY_BIT_RATE, 1024 * 1000);//码率
mediaformat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2);//I帧间隔
mCodec.configure(mediaformat, null, null, 0);//注意第二个参数可以填入surface,将解码数据直接渲染,这里我用的自己的渲染故填null
mCodec.start();//启动解码器

编解码器的设置差不多,只是多了sps和pps的参数设定。

喂数据解码

public void onFrame(UiVideoData uiVideoData) {
    byte[] buf = uiVideoData._data;
    ByteBuffer[] inputBuffers = mCodec.getInputBuffers();//MediaCodec在此ByteBuffer[]中获取输入数据
    ByteBuffer[] outputBuffers = mCodec.getOutputBuffers(); // 解码后的数据
    int inputBufferIndex = mCodec.dequeueInputBuffer(100);//获取输入缓冲区的索引
    if (inputBufferIndex >= 0) {
        long pts = computePresentationTime(generateIndex);
        ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
        inputBuffer.clear();
        inputBuffer.put(buf);//先获取缓冲区,再放入值
        mCodec.queueInputBuffer(inputBufferIndex, 0, buf.length, pts, 0);//四个参数,第一个是输入缓冲区的索引,第二个是放入的数据大小,第三个是时间戳,保证递增就是
        generateIndex += 1;
    } else {
        return;
    }
    MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();//用于描述解码得到的byte[]数据的相关信息
    int outputBufferIndex = mCodec.dequeueOutputBuffer(bufferInfo, 1000);//-1代表一直等待,0表示不等待
    Log.e(TAG, "outputBufferIndex=" + outputBufferIndex);
    while (outputBufferIndex >= 0) {//大于等于0表示解码器有数据输出
        ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
        byte[] outData = new byte[bufferInfo.size];
        outputBuffer.get(outData);//将Buffer内的数据取出到字节数组中
        mCodec.releaseOutputBuffer(outputBufferIndex, true);
        outputBufferIndex = -1;
    }
}
这里也基本是模板代码 outputBuffer.get(outData)获取到解码后的数据然后就可以自己去渲染的。

注:Android硬编硬解受限于设备并不保证所有设备都能运行成功。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值