android 使用MediaCodec硬解码H264裸流并使用SurfaceView进行展示

场景说明

公司要做个实时视频的项目,用的RK3399的开发板,通过开发板的网口接收H264裸流数据并展示。开发板上跑Android 9,因为3399这个板子上有ARM 的mali T860MP4 GPU,T860主要面向中端的高分辨率需求,支持4K视频功耗也比此前更低,性能还是不错的,所以选择使用MediaCodec进行硬解。

获取UDP数据

通过开发板的网口获取UDP数据这块,网上代码很多一搜一大堆,这里简单罗列下我的代码:

		//初始化udp连接
        try {
        	//6666是指定的推送到的网络端口
            udpClient = new DatagramSocket(6666);
            if (receivePacket == null) {
                // 创建接受数据的 packet
                receivePacket = new DatagramPacket(receiveByte, BUFFER_LENGTH);
            }

        } catch (SocketException e) {
            e.printStackTrace();
        }
	/**
     * 接收Udp数据
     */
    private void startUdpSocketThread() {
        clientUdphread = new Thread(new Runnable() {
            @Override
            public void run() {
                receiveMessage();
            }
        });
        isThreadRunning = true;
        clientUdphread.start();
    }
	/**
     * 处理接受到的UDP消息
     */
    private void receiveMessage() {
        Log.d(TAG, "receiveMessage start");
        while (isThreadRunning) {

            try {
                if (udpClient != null) {
                    udpClient.receive(receivePacket);
                }
            } catch (IOException e) {
                Log.e(TAG, "UDP数据包接收失败!stopUDPSocket");
                stopUDPSocket();
                e.printStackTrace();
                return;
            }

            if (receivePacket == null || receivePacket.getLength() == 0) {
                Log.e(TAG, "无法接收UDP数据或者接收到的UDP数据为空");
                continue;
            }
			//H264数据是以00 00 00 01开头的,通过这个标志判断是新的一帧,将数据放入tempByteBuffer,tempByteBuffer设置的大小是200*1024,receiveByte设置的是8192
            if("0".equals(Integer.toHexString(receivePacket.getData()[0] & 0x0FF))
                    && "0".equals(Integer.toHexString(receivePacket.getData()[1] & 0x0FF))
                    && "0".equals(Integer.toHexString(receivePacket.getData()[2] & 0x0FF))
                    && "1".equals(Integer.toHexString(receivePacket.getData()[3] & 0x0FF))) {
                //Log.d(TAG, "帧类型="+Integer.toHexString(receivePacket.getData()[4] & 0x0FF)+",tempByteBuffer大小="+tempByteBuffer.position());
                //这里将一帧数据传给mediacodec
                onFrame(tempByteBuffer);
                tempByteBuffer.clear();
                tempByteBuffer.put(receivePacket.getData(),0,receivePacket.getLength());
                //Log.d(TAG, "tempByteBuffer大小="+tempByteBuffer.position());
            }else{
                tempByteBuffer.put(receivePacket.getData(),0,receivePacket.getLength());
                //Log.d(TAG, "tempByteBuffer大小="+tempByteBuffer.position());
                //onFrame()
            }
            //onFrame(receivePacket.getData(),0,BUFFER_LENGTH);

            // 每次接收完UDP数据后,重置长度。否则可能会导致下次收到数据包被截断。
            if (receivePacket != null) {
                receivePacket.setLength(BUFFER_LENGTH);
            }
        }
    }

初始化解码器

这里要注意的是mediacodec的configure方法必须在surfaceview准备后之后才可以,所以写在监听里了。

 try {
            //创建解码器
            zMediaCodec = MediaCodec.createDecoderByType(MIME_TYPE);
        }catch (IOException e){
            e.printStackTrace();
            Log.d(TAG, "MediaCodec decode IOException");
        }
        mediaFormat = MediaFormat.createVideoFormat(MIME_TYPE, 1920, 1080);
        //设置帧率
        mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
        zSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
            @Override
            public void surfaceCreated(SurfaceHolder surfaceHolder) {
                Log.d(TAG, "surfaceCreated");
                zMediaCodec.configure(mediaFormat, zSurfaceView.getHolder().getSurface(), null, 0);
                zMediaCodec.start();
                Log.d(TAG, "MediaCodec start");
                startUdpSocketThread();
            }

            @Override
            public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
                Log.d(TAG, "surfaceChanged");
            }

            @Override
            public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
                mediaCodecstop();
            }
        });

解码一帧

最开始犯了一个错误,把udp接收到的数据调用onframe,onframe接收的是一帧的数据,所以保证传输的数据是一帧的

	/**
     * 处理帧
     */
    public boolean onFrame(ByteBuffer buf) {
        //Log.e("Media", "onFrame start");
        //Log.e("Media", "onFrame Thread:" + Thread.currentThread().getId());
        // Get input buffer index
        @SuppressWarnings("deprecation")
        ByteBuffer[] inputBuffers = zMediaCodec.getInputBuffers();
        int inputBufferIndex = zMediaCodec.dequeueInputBuffer(0);

        //Log.e("Media", "onFrame index:" + inputBufferIndex);
        if (inputBufferIndex >= 0) {
            ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
            inputBuffer.clear();
            //这个用法挺不好
            for(int i=0;i<buf.position();i++){
                inputBuffer.put(buf.get(i));
            }
            zMediaCodec.queueInputBuffer(inputBufferIndex, 0, inputBuffer.position(), 0, 0);
            //mCount++;
        } else {
            return false;
        }

        // Get output buffer index
        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
        int outputBufferIndex = zMediaCodec.dequeueOutputBuffer(bufferInfo, 100);
        while (outputBufferIndex >= 0) {
            zMediaCodec.releaseOutputBuffer(outputBufferIndex, true);
            outputBufferIndex = zMediaCodec.dequeueOutputBuffer(bufferInfo, 0);
        }
        //Log.e("Media", "onFrame end");
        return true;
    }

后续

后续还会加推流的功能,把获取到的视频流通过rtmp或rtsp推送到流媒体服务器或公共的直播平台上。

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
MediaCodecAndroid 提供的一个用于音视频编解码的 API,通过 MediaCodec 可以实现对 H.264 等常见视频格式的解码。下面是一个简单的使用 MediaCodec 解码 H.264 的示例: ```java // 创建 MediaCodec 对象 MediaCodec codec = MediaCodec.createDecoderByType("video/avc"); // 配置 MediaCodec,设置输入数据格式和输出数据 Surface MediaFormat format = MediaFormat.createVideoFormat("video/avc", width, height); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible); codec.configure(format, surface, null, 0); // 启动 MediaCodec codec.start(); // 循环读取 H.264 数据并进行解码 while (decoding) { int inputBufferIndex = codec.dequeueInputBuffer(-1); if (inputBufferIndex >= 0) { ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferIndex); // 将 H.264 数据写入 inputBuffer 中 codec.queueInputBuffer(inputBufferIndex, 0, data.length, presentationTimeUs, 0); } MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); int outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, 0); if (outputBufferIndex >= 0) { ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferIndex); // 处理解码后的 YUV 数据 codec.releaseOutputBuffer(outputBufferIndex, true); } } ``` 需要注意的是,在使用 MediaCodec 解码 H.264 数据时,需要将 H.264 数据先解析成 NAL 单元,再将 NAL 单元写入到 inputBuffer 中进行解码。另外,解码后的数据是 YUV 格式的数据,需要根据实际需求进行处理。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值