编解码播放大数据量流问题

服务器收到解码的流数据后进行编码再播放,当数据量大的时候,长时间播放总是会导致内存问题,板子会自动重启,请问我代码哪里存在问题?


jni层服务器返回的解码后的数据

				/**
         * 根据资源id存放,jni回调的解码数据
         */
        public static HashMap<Integer, LinkedBlockingQueue<FrameData>> decodeMap = new HashMap<>();
        //对象池
        public static LinkedBlockingQueue<FrameData> frameDataPool = new LinkedBlockingQueue<>(50);


        /**
         * jni层返回解码后的数据
         * @param res     播放资源ID
         * @param codecid 解码器类型 (h264=27,h265=173,mpeg4=12,vp8=139,vp9=167)
         * @param w       视频的宽
         * @param h       视频的高
         * @param packet  解码数据
         */
        public int callback_videodecode(int isKeyframe, int res, int codecid, int w, int h, byte[] packet, long pts, byte[] codecdata) {
            if (packet != null) {
                FrameData frameData = frameDataPool.poll();
                if (frameData == null) {
                    //对象池中没有了就新建一个
                    frameData = new FrameData();
                }
                frameData.setIsKeyFrame(isKeyframe);
                frameData.setRes(res);
                frameData.setCodecid(codecid);
                frameData.setW(w);
                frameData.setH(h);
                frameData.setPts(pts);
                frameData.setBytes(packet);
                frameData.setCodecdata(codecdata);
                LinkedBlockingQueue<FrameData> decodeQueue = decodeMap.get(res);
                decodeQueue.offer(frameData);
            }
            return 0;
        }

自定义播放的view

public class PlayerView extends SurfaceView implements SurfaceHolder.Callback {
    private DecodeThread decodeThread;
    private Surface mSurface;
    private int resourceId = 0;
    private final AtomicBoolean mIsRunning = new AtomicBoolean(true);

    public PlayerView(Context context) {
        super(context);
        init();
    }

    public PlayerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public PlayerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    public PlayerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    private void init() {
        getHolder().addCallback(this);
    }

    public int getResourceId() {
        return resourceId;
    }

    public void setResourceId(int resourceId) {
        this.resourceId = resourceId;
    }

    @Override
    public void surfaceCreated(@NonNull SurfaceHolder holder) {
        mSurface = holder.getSurface();
        startDecode();
    }

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

    }

    @Override
    public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
        stopDecode();
    }

    public void startDecode() {
        if (decodeThread == null) {
            mIsRunning.set(true);
            decodeThread = new DecodeThread("pure-" + resourceId);
            LogUtils.i("---startDecode---" + decodeThread);
            decodeThread.start();
        }
    }

    public void stopDecode() {
        if (decodeThread != null) {
            LogUtils.i("---stopDecode---" + decodeThread);
            mIsRunning.set(false);
            decodeThread.interrupt();
            decodeThread = null;
        }
    }

    class DecodeThread extends Thread {

        private MediaCodec mediaCodec;
        private final MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
        private final LinkedBlockingQueue<FrameData> cacheQueue = new LinkedBlockingQueue<>(25);
        private String saveMimeType = "";
        private int initW;
        private int initH;

        public DecodeThread(@NonNull String name) {
            super(name);
        }

        @Override
        public void run() {
            try {
                LinkedBlockingQueue<FrameData> decodeQueue = CallValue.decodeMap.get(resourceId);
                byte[] packet;
                int packetSize;
                int width, height, codecid, isKeyFrame;
                long pts;
                String mimeType;
                FrameData bean;
                int cacheQueueSize, decodeQueueSize;
                while (mIsRunning.get()) {
                    cacheQueueSize = cacheQueue.size();
                    decodeQueueSize = decodeQueue.size();
                    if (mSurface == null) {
                        if (decodeQueueSize > 0) {
                            //回收对象
                            if (!CallValue.frameDataPool.offer(decodeQueue.poll())) {
                                LogUtils.i("---recycle object err---");
                            } else {
                                LogUtils.i("---recycle object---");
                            }
                        }
                        //跳过循环
                        continue;
                    }
                    if (decodeQueueSize > 0 || cacheQueueSize > 0) {
                        //先取缓存的数据
                        bean = cacheQueueSize > 0 ? cacheQueue.peek() : decodeQueue.poll();
                        packet = bean.getBytes();
                        packetSize = bean.getPacketSize();
                        width = bean.getW();
                        height = bean.getH();
                        isKeyFrame = bean.getIsKeyFrame();
                        if (initW != 0 && initW != width) {
                            LogUtils.i("宽度改变:" + width);
                        }
                        if (initH != 0 && initH != height) {
                            LogUtils.i("高度改变:" + height);
                        }
                        pts = bean.getPts();
                        codecid = bean.getCodecid();
                        mimeType = getMimeType(codecid);
                        //已经初始化过了,但是参数有变化的时候需要重新初始化
                        if (!saveMimeType.equals(mimeType) || initW != width || initH != height || mediaCodec == null) {
                            //进入初始化MediaCodec
                            if (mediaCodec != null) {
                                LogUtils.i("重新配置mediaCodec");
                                //调用stop方法使其进入 uninitialzed 状态,这样才可以重新配置MediaCodec
                                mediaCodec.stop();
                            }
                            saveMimeType = mimeType;
                            //初始化MediaCodec
                            initCodec(width, height, bean.getCodecdata());
                        }
                        if (mediaCodec == null) {//初始化失败
                            if (cacheQueueSize > 0) {//使用的是缓存中的数据
                                //把它删掉
                                FrameData poll = cacheQueue.poll();
                                //回收对象,已经满了再调用offer添加数据会返回false
                                CallValue.frameDataPool.offer(poll);
                            } else {//使用的是新数据
                                //放入缓存队列
                                if (!cacheQueue.offer(bean)) {
                                    //已经满了,把旧的删除掉
                                    cacheQueue.poll();
                                    //然后将这个新的放入
                                    cacheQueue.offer(bean);
                                }
                            }
                            return;
                        }
                        if (mediaCodecDecode(packet, pts, packetSize)) {
                            if (cacheQueueSize > 0) {
                                //使用缓存数据,编码成功就丢掉
                                cacheQueue.poll();
                            }
                            //回收对象,已经满了再调用offer添加数据会返回false
                            if (!CallValue.frameDataPool.offer(bean)) {
                                LogUtils.i("---recycle object---");
                                bean = null;
                            }
                        } else {//编码失败
                            if (cacheQueueSize == 0) {//使用的是新数据
                                LogUtils.i("after encoding fails, put it into the cache queue:" + bean.toString());
                                //放入缓存队列
                                if (!cacheQueue.offer(bean)) {
                                    //已经满了,把旧的删除掉
                                    cacheQueue.poll();
                                    //然后将这个新的放入
                                    cacheQueue.offer(bean);
                                }
                            }
                        }
                    } else {
                        if (mediaCodec != null) {
                            //外部没有数据了,还需要渲染已经放入解码器中的数据
                            mediaCodecDecode(null, 0, 0);
                        }
                    }
                }
                cacheQueue.clear();//清空缓存队列
                releaseMediaCodec();
                LogUtils.i("thread id=" + Thread.currentThread().getId() + ",end decoding decodeQueue=" + decodeQueue.size());
                CallValue.frameDataPool.clear();
                decodeQueue.clear();//清空接收队列,避免下次存放时留下脏数据
            } catch (Exception e) {
                LogUtils.e(e);
                e.printStackTrace();
            }
        }


        /**
         * 初始化解码器
         *
         * @param w         宽
         * @param h         高
         * @param codecdata pps/sps 编码配置数据
         */
        private void initCodec(int w, int h, byte[] codecdata) {
            try {
                LogUtils.i("initCodec : ---start--- ");
                //创建了一个解码器,此时编解码器处于未初始化状态(Uninitialized)
                mediaCodec = MediaCodec.createDecoderByType(saveMimeType);
                //宽高要判断是否是解码器所支持的范围
                MediaCodecInfo.CodecCapabilities capabilitiesForType = mediaCodec.getCodecInfo().getCapabilitiesForType(saveMimeType);
                MediaCodecInfo.VideoCapabilities videoCapabilities = capabilitiesForType.getVideoCapabilities();
                Range<Integer> supportedWidths = videoCapabilities.getSupportedWidths();
                Range<Integer> supportedHeights = videoCapabilities.getSupportedHeights();
                initW = w;
                initH = h;
                if (w < h) {
                    LogUtils.i("宽度比高度小:w=" + w + ",h=" + h);
                    int temp = w;
                    w = h;
                    h = temp;
                }
                w = supportedWidths.clamp(w);
                h = supportedHeights.clamp(h);
                LogUtils.d("initCodec 可支持的宽高:" + supportedWidths + "、" + supportedHeights + ",initW=" + initW + ",initH=" + initH);
                MediaFormat mediaFormat = MediaFormat.createVideoFormat(saveMimeType, w, h);
                mediaFormat.setInteger(MediaFormat.KEY_WIDTH, w);
                mediaFormat.setInteger(MediaFormat.KEY_HEIGHT, h);
                if (codecdata != null && codecdata.length > 0) {
                    LogUtils.i("initCodec 设置 csd= " + Arrays.toString(codecdata));
                    mediaFormat.setByteBuffer("csd-0", ByteBuffer.wrap(codecdata));
                    mediaFormat.setByteBuffer("csd-1", ByteBuffer.wrap(codecdata));
                }
                boolean formatSupported = capabilitiesForType.isFormatSupported(mediaFormat);
                LogUtils.i("initCodec :  是否支持 --> " + formatSupported + ",mediaFormat=" + mediaFormat);
                //2.对编解码器进行配置,这将使编解码器转为配置状态(Configured)
                mediaCodec.configure(mediaFormat, mSurface, null, 0);
                LogUtils.i("initCodec : ---configure--- ");
                //3.调用start()方法使其转入执行状态(Executing)
                mediaCodec.start();
                LogUtils.i("initCodec : ---end--- ");
                //要保持屏幕纵横比:此方法必须在configure和start之后执行才有效
                //VIDEO_SCALING_MODE_SCALE_TO_FIT //默认铺满
                //VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING //保持比例,多余会被裁剪
//                mediaCodec.setVideoScalingMode(MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT);
            } catch (Exception e) {
                LogUtils.e(e);
                e.printStackTrace();
            }
        }

        /**
         * 解码数据
         *
         * @param packet     帧数据包
         * @param pts        此缓冲区的呈现时间戳(以微秒为单位)。这通常是应该呈现(渲染)此缓冲区的媒体时间。使用输出表面时,这将作为帧的时间戳传播(转换为纳秒后)
         * @param packetSize 帧数据大小
         * @return 成功将数据放入解码器则返回true
         */
        private boolean mediaCodecDecode(byte[] packet, long pts, int packetSize) {
            int inputBufferIndex = -1;
            //判断解码器是否初始化完成
            if (mediaCodec == null) {
                LogUtils.e("mediaCodec not ready");
                return false;
            }
            try {
                if (packet != null) {
                    //队列中有视频帧,检查解码队列中是否有空闲可用的buffer,有则取视频帧送进去解码
                    inputBufferIndex = mediaCodec.dequeueInputBuffer(0);
                    if (inputBufferIndex >= 0) {
                        //有空闲可用的解码buffer
                        ByteBuffer byteBuffer = mediaCodec.getInputBuffer(inputBufferIndex);
                        byteBuffer.clear();
                        byteBuffer.limit(packetSize);
                        byteBuffer.position(0);
                        //将视频队列中的头取出送到解码队列中
                        byteBuffer.put(packet, 0, packetSize);
                        mediaCodec.queueInputBuffer(inputBufferIndex, 0, packetSize, pts, 0);
                    }
                }
                int outputBufferIndex = mediaCodec.dequeueOutputBuffer(info, 0);
                if (outputBufferIndex >= 0) {
                    //如果配置编码器时指定了有效的surface,传true将此输出缓冲区显示在surface
                    mediaCodec.releaseOutputBuffer(outputBufferIndex, true);
                }
            } catch (Exception e) {
                e.printStackTrace();
                LogUtils.e(e);
            }
            return inputBufferIndex >= 0;
        }

        /**
         * 释放资源
         */
        private void releaseMediaCodec() {
            if (mediaCodec != null) {
                try {
                    LogUtils.e("---releaseMediaCodec---");
                    //使用完编解码器后,你必须调用release()方法释放其资源
                    mediaCodec.release();
                } catch (Exception e) {
                    LogUtils.e(e);
                    e.printStackTrace();
                }
                mediaCodec = null;
            }
        }
        
        /**
         * 获取指定的MimeType
         *
         * @param codecId 后台回调ID
         * @return MimeType
         */
        private String getMimeType(int codecId) {
            switch (codecId) {
                case 12:
                case 13:
                    return "video/mp4v-es";
                case 139:
                case 140:
                    return "video/x-vnd.on2.vp8";
                case 167:
                case 168:
                    return "video/x-vnd.on2.vp9";
                case 173:
                case 174:
                    return "video/hevc";
                default:
                    return "video/avc";
            }
        }
    }
}


MediaCodec
编解码
Android
2023-02-20 14:42:41
浏览
收藏 0
回答 0
待解决
相关问题
图片编解码支持的格式有哪些
745浏览 • 1回复 待解决
HarmonyOS编解码接口标准
27浏览 • 1回复 待解决
sql数据库查询数据量太大查不出来?
2698浏览 • 1回复 待解决
图片编解码能力支持哪些格式
788浏览 • 1回复 待解决
如何实现字符串编解码
1091浏览 • 1回复 待解决
ArkTS如何实现字符串编解码
1305浏览 • 1回复 待解决
播放器】硬解码支持的Demo
729浏览 • 1回复 待解决
中文字符串的编解码,有人知道吗?
290浏览 • 1回复 待解决
java播放器怎么用解码器?
3791浏览 • 1回复 待解决
如何解决预览黑屏的问题
411浏览 • 1回复 待解决
如何连续获取相机预览数据
185浏览 • 1回复 待解决