音视频6.4——h264文件解码输出到Surface

本文详细介绍了Android平台下使用MediaCodec进行音视频开发的步骤,包括获取Surface、初始化MediaCodec、解码视频帧以及将解码后的数据渲染到Surface上。通过实例代码展示了如何从摄像头获取YUV数据,然后进行H264解码,最后将解码后的视频帧显示在屏幕上。这对于理解Android音视频处理流程非常有帮助。
摘要由CSDN通过智能技术生成

音视频开发路线:

Android 音视频开发入门指南_Jhuster的专栏的技术博客_51CTO博客_android 音视频开发入门

demo地址:

GitHub - wygsqsj/videoPath: 音视频学习路线demo

获取Surface

首先获取surface,我们MediaCodec解析出来的数据需要放到Surface中进行渲染,获取到surface之后开启解码线程

 protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_demo6);
        surfaceview = findViewById(R.id.demo6Surface);
        surfaceview.getHolder().addCallback(this);
 }

......

  //摄像头获取到的yuv数据回调
    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
       new H264DecodeThread(this, width, height, framerate, biterate, surface).start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

初始化MediaCodec

将我们初始化好的surface出入给MediaCodec,这样在我们的codec解码完数据后可以直接调用

releaseOutputBuffer(outputIndex, true); 第二个参数指定为true代表把当前的数据输出给surface,我们便可以看到视频了

//初始化codec
decodeCodec = MediaCodec.createDecoderByType("video/avc");
//初始化编码器
final MediaFormat mediaformat = MediaFormat.createVideoFormat("video/avc", width, height);

//设置帧率
mediaformat.setInteger(MediaFormat.KEY_FRAME_RATE, biterate);
decodeCodec.configure(mediaformat, surface, null, 0);
decodeCodec.start();

获取数据,进行解码

以分隔符0x00000001为基准,每次读取一个NALU单元的数据给MediaCodec解析,由于当前我们的数据较少,所以暂时一次全部读到byte数组中

/**
 * 获取264所有的数据,一次性读取到内存中
 */
private byte[] getBytes() throws IOException {
    int len;
    int size = 1024;
    byte[] bytes;
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    bytes = new byte[1024];
    while ((len = is.read(bytes, 0, size)) != -1) {
        bos.write(bytes, 0, len);
    }
    bytes = bos.toByteArray();
    return bytes;
}

开启循环,找到当前NALU的数据,交给MediaCodec,需要注意的是,获取NALU分隔符时候,起始位置需要跳过几个字节,如果不跳过,每次都从分隔符前开始找,永远都是读到当前的分隔符就返回了,而我们需要找到下一个分隔符的位置,所以要跳过几个字节,不让读取当前第一个分隔符

byte[] bytes = getBytes();
//开始索引和当前得索引
int startIndex = 0, nextIndex = 0;
int totalSize = bytes.length;
Log.i(LOG_TAG, "当前的数据大小" + totalSize);
MediaCodec.BufferInfo decodeBufferInfo = new MediaCodec.BufferInfo();//用于描述解码得到的byte[]数据的相关信息
while (true) {
    Log.i(LOG_TAG, "当前的startIndex" + startIndex);
    if (startIndex >= totalSize) {
        break;
    }
    //sps为所有h264文件的起始值,也就是所有的h264起始值时0x01,所以加上一个随意的值,前面的sps就匹配不了了,否则nextIndex会永远读取不到数据
    nextIndex = findByFrame(bytes, startIndex + 1);
    Log.i(LOG_TAG, "当前的nextIndex" + nextIndex);
    if (nextIndex == -1) {
        break;
    }
    //获取codec输入数据载体
    int inputIndex = decodeCodec.dequeueInputBuffer(10000);
    if (inputIndex != -1) {
        Log.i(LOG_TAG, "找到了input 小推车" + inputIndex);
        ByteBuffer[] byteBuffers = decodeCodec.getInputBuffers();
        ByteBuffer inputBuffer = decodeCodec.getInputBuffer(inputIndex);
        inputBuffer.clear();
        //把下一帧放入解码缓存
        inputBuffer.put(bytes, startIndex, nextIndex - startIndex);
        decodeCodec.queueInputBuffer(inputIndex, 0, nextIndex - startIndex, 0, 0);
        //下一帧获取从当前末尾开始
        startIndex = nextIndex;
    } else {
        Log.i(LOG_TAG, "没有可用的input 小推车");
    }

    //获取codec解码好的数据
    int outputIndex = decodeCodec.dequeueOutputBuffer(decodeBufferInfo, 10000);//返回当前筐的标记
    switch (outputIndex) {
        case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED:
            Log.i(LOG_TAG, "输出的format已更改" + decodeCodec.getOutputFormat());
            break;
        case MediaCodec.INFO_TRY_AGAIN_LATER:
            Log.i(LOG_TAG, "超时,没获取到");
            break;
        case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED:
            Log.i(LOG_TAG, "输出缓冲区已更改");
            break;
        default:
            //config时已经把surface传给了codec,释放一下,第二个参数指定为true,codec自动会渲染到surface中
            decodeCodec.releaseOutputBuffer(outputIndex, true);
            break;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值