Android性能优化:直播推流使用对象池

#性能优化之Android应用内存优化实战#

对应视频:3. 为什么要学习数据结构与算法_哔哩哔哩_bilibili

根据Android Handler里面的Message实现思想,实现自定义的对象池

public class RTMPPackage {
    private static final String TAG = "RTMPPackage";

    public static final int RTMP_PACKET_TYPE_VIDEO = 0;
    public static final int RTMP_PACKET_TYPE_AUDIO_HEAD = 1;
    public static final int RTMP_PACKET_TYPE_AUDIO_DATA = 2;

    private byte[] buffer; // 图像数据
    private int type;
    private long tms;
    private int size;

    public static RTMPPackage EMPTY_PACKAGE = new RTMPPackage();


    public byte[] getBuffer() {
        return buffer;
    }

    public void setBuffer(byte[] buffer) {
        this.buffer = buffer;
    }

    public int getType() {
        return type;
    }

    public void setType(int type) {
        this.type = type;
    }

    public void setTms(long tms) {
        this.tms = tms;
    }

    public long getTms() {
        return tms;
    }

    public int getSize() {
        return size;
    }

    public void setSize(int size) {
        this.size = size;
    }

    RTMPPackage next;
    //同步对象
    public static final Object sPoolSync = new Object();
    private static RTMPPackage sPool; // 链表第一个元素(对象池)
    private static int sPoolSize = 0; //对象池中对象的个数

    private static final int MAX_POOL_SIZE = 50;


    public static RTMPPackage obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                RTMPPackage m = sPool; //链表第一个元素,返回出去 复用
                sPool = m.next;
                m.next = null;
                sPoolSize--;
                return m;
            }
        }
        return new RTMPPackage();
    }


    public void recycle() {
        buffer = null;
        type = -1;
        tms = -1;
        size = 0;
        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

}

相关类:

VideoCodec.java

public class VideoCodec extends Thread {

    private ArrayPool arrayPool;
    private ScreenLive screenLive;
    private MediaCodec mediaCodec;
    private VirtualDisplay virtualDisplay;
    private boolean isLiving;
    private long timeStamp;
    private long startTime;
    private MediaProjection mediaProjection;

    public VideoCodec(ScreenLive screenLive, ArrayPool arrayPool) {
        this.screenLive = screenLive;
        this.arrayPool = arrayPool;
    }


    @Override
    public void run() {
        isLiving = true;
        mediaCodec.start();
        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();

        while (isLiving) {
            if (timeStamp != 0) {
                //2000毫秒后 mediacodec虽然设置了关键帧间隔,但是没用 需要手动强制请求
                if (System.currentTimeMillis() - timeStamp >= 2_000) {
                    Bundle params = new Bundle();
                    //立即刷新 让下一帧是关键帧
                    params.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
                    mediaCodec.setParameters(params);
                    timeStamp = System.currentTimeMillis();
                }
            } else {
                timeStamp = System.currentTimeMillis();
            }
            int index = mediaCodec.dequeueOutputBuffer(bufferInfo, 10);
            if (index >= 0) {
                ByteBuffer buffer = mediaCodec.getOutputBuffer(index);
                byte[] outData = arrayPool.get(bufferInfo.size);
                buffer.get(outData, 0, bufferInfo.size);
                //sps pps
//                ByteBuffer sps = mediaCodec.getOutputFormat().getByteBuffer
//                        ("csd-0");
//                ByteBuffer pps = mediaCodec.getOutputFormat().getByteBuffer
//                        ("csd-1");
                if (startTime == 0) {
                    // 微妙转为毫秒
                    startTime = bufferInfo.presentationTimeUs / 1000;
                }

                RTMPPackage rtmpPackage = RTMPPackage.obtain();
                rtmpPackage.setBuffer(outData);
                rtmpPackage.setSize(bufferInfo.size);
                rtmpPackage.setType(RTMPPackage.RTMP_PACKET_TYPE_VIDEO);
                long tms = (bufferInfo.presentationTimeUs / 1000) - startTime;
                rtmpPackage.setTms(tms);
                screenLive.addPackage(rtmpPackage); //入队列
                mediaCodec.releaseOutputBuffer(index, false);
            }
        }
        isLiving = false;
        startTime = 0;
        mediaCodec.stop();
        mediaCodec.release();
        mediaCodec = null;
        virtualDisplay.release();
        virtualDisplay = null;
        mediaProjection.stop();
        mediaProjection = null;
    }


    public void startLive(MediaProjection mediaProjection) {
        this.mediaProjection = mediaProjection;
        // 配置编码参数
        MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC,
                640,
                480);
        format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
                MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
        format.setInteger(MediaFormat.KEY_BIT_RATE, 500_000);
        format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2);
        try {
            // 创建编码器
            mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
            mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            // 从编码器创建一个画布, 画布上的图像会被编码器自动编码
            Surface surface = mediaCodec.createInputSurface();


            virtualDisplay = mediaProjection.createVirtualDisplay(
                    "screen-codec",
                    640, 480, 1,
                    DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
                    surface, null, null);

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


        start();
    }

    public void stopLive() {
        isLiving = false;
        try {
            join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

AudioCodec.java 

public class AudioCodec extends Thread {

    private final ScreenLive screenLive;
    private final ArrayPool arrayPool;


    private MediaCodec mediaCodec;
    private AudioRecord audioRecord;
    private boolean isRecoding;

    private long startTime;
    private int minBufferSize;

    public AudioCodec(ScreenLive screenLive, ArrayPool arrayPool) {
        this.screenLive = screenLive;
        this.arrayPool = arrayPool;
    }


    @Override
    public void run() {
        isRecoding = true;
        RTMPPackage rtmpPackage = new RTMPPackage();
        byte[] audioDecoderSpecificInfo = {0x12, 0x08};
        rtmpPackage.setBuffer(audioDecoderSpecificInfo);
        rtmpPackage.setType(RTMPPackage.RTMP_PACKET_TYPE_AUDIO_HEAD);
        screenLive.addPackage(rtmpPackage);
        audioRecord.startRecording();
        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
        byte[] buffer = new byte[minBufferSize];

        while (isRecoding) {
            int len = audioRecord.read(buffer, 0, buffer.length);
            if (len <= 0) {
                continue;
            }
            //立即得到有效输入缓冲区
            int index = mediaCodec.dequeueInputBuffer(0);
            if (index >= 0) {
                ByteBuffer inputBuffer = mediaCodec.getInputBuffer(index);
                inputBuffer.clear();
                inputBuffer.put(buffer, 0, len);
                //填充数据后再加入队列
                mediaCodec.queueInputBuffer(index, 0, len,
                        System.nanoTime() / 1000, 0);
            }
            index = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);

            while (index >= 0 && isRecoding) {
                ByteBuffer outputBuffer = mediaCodec.getOutputBuffer(index);
                byte[] outData = arrayPool.get(bufferInfo.size);
                outputBuffer.get(outData, 0, bufferInfo.size);

                if (startTime == 0) {
                    startTime = bufferInfo.presentationTimeUs / 1000;
                }
                rtmpPackage = RTMPPackage.obtain();
                rtmpPackage.setBuffer(outData);
                rtmpPackage.setSize(bufferInfo.size);
                rtmpPackage.setType(RTMPPackage.RTMP_PACKET_TYPE_AUDIO_DATA);
                long tms = (bufferInfo.presentationTimeUs / 1000) - startTime;
                rtmpPackage.setTms(tms);
                screenLive.addPackage(rtmpPackage);
                mediaCodec.releaseOutputBuffer(index, false);
                index = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
            }
        }
        audioRecord.stop();
        audioRecord.release();
        audioRecord = null;

        mediaCodec.stop();
        mediaCodec.release();
        mediaCodec = null;
        startTime = 0;
        isRecoding = false;
    }

    public void startLive() {

        try {

            /**
             * 获得创建AudioRecord所需的最小缓冲区
             * 采样+单声道+16位pcm
             */
            minBufferSize = AudioRecord.getMinBufferSize(44100,
                    AudioFormat.CHANNEL_IN_MONO,
                    AudioFormat.ENCODING_PCM_16BIT);
            /**
             * 创建录音对象
             * 麦克风+采样+单声道+16位pcm+缓冲区大小
             */
            audioRecord = new AudioRecord(
                    MediaRecorder.AudioSource.MIC, 44100,
                    AudioFormat.CHANNEL_IN_MONO,
                    AudioFormat.ENCODING_PCM_16BIT, minBufferSize);

            MediaFormat format = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 44100,
                    1);
            format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel
                    .AACObjectLC);
            format.setInteger(MediaFormat.KEY_BIT_RATE, 64_000);
            format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, minBufferSize * 2);
            mediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
            mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            mediaCodec.start();
        } catch (IOException e) {
            e.printStackTrace();
        }

        start();
    }

    public void stopLive() {
        isRecoding = false;
        try {
            join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

ScreenLive.java 

public class ScreenLive implements Runnable {

    static {
        System.loadLibrary("native-lib");
    }

    private String url;
    private MediaProjectionManager mediaProjectionManager;
    private boolean isLiving;
    private LinkedBlockingQueue queue = new LinkedBlockingQueue<>();
    private MediaProjection mediaProjection;


    public void startLive(Activity activity, String url) {
        this.url = url;
        // 投屏管理器
        this.mediaProjectionManager = (MediaProjectionManager) activity
                .getSystemService(Context.MEDIA_PROJECTION_SERVICE);
        // 创建截屏请求intent
        Intent captureIntent = mediaProjectionManager.createScreenCaptureIntent();
        activity.startActivityForResult(captureIntent, 100);
    }

    public void stoptLive() {
        addPackage(RTMPPackage.EMPTY_PACKAGE);
        isLiving = false;
    }

    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        // 用户授权
        if (requestCode == 100 && resultCode == Activity.RESULT_OK) {
            // 获得截屏器
            mediaProjection = mediaProjectionManager.getMediaProjection
                    (resultCode, data);
            LiveTaskManager.getInstance().execute(this);
        }
    }

    public void addPackage(RTMPPackage rtmpPackage) {
        if (!isLiving) {
            return;
        }
        queue.add(rtmpPackage);
    }

    private static final String TAG = "ScreenLive";

    @Override
    public void run() {
        //1、连接服务器  斗鱼rtmp服务器
        if (!connect(url)) {
            disConnect();
            return;
        }
        Log.i(TAG, "连接成功 准备推流 ==========================");
        isLiving = true;
        ArrayPool arrayPool = new LruArrayPool();
        VideoCodec videoCodec = new VideoCodec(this,arrayPool);
        videoCodec.startLive(mediaProjection);
        AudioCodec audioCodec = new AudioCodec(this,arrayPool);
        audioCodec.startLive();
        boolean isSend = true;

        while (isLiving && isSend) {
            RTMPPackage rtmpPackage = null;
            try {
                checkDrop();
                rtmpPackage = queue.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (null == rtmpPackage) {
                break;
            }
            byte[] buffer = rtmpPackage.getBuffer();
            if (buffer != null && buffer.length != 0) {
                isSend = sendData(buffer, rtmpPackage.getSize(),
                        rtmpPackage.getType(), rtmpPackage.getTms());
//                Log.i(TAG, "sendData: ==========================" + isSend);
            }
            arrayPool.put(buffer);
            rtmpPackage.recycle();
        }
        isLiving = false;
        videoCodec.stopLive();
        audioCodec.stopLive();
        queue.clear();
        disConnect();
    }

    private void checkDrop() throws InterruptedException {
        while (queue.size() > 200) {
            queue.take();
        }
    }

    /**
     * 与斗鱼服务器建立rtmp连接
     *
     * @param url
     * @return
     */
    private native boolean connect(String url);

    private native void disConnect();

    /**
     * 发送音视频数据到c
     *
     * @param data
     * @param len
     * @param type
     * @param tms
     * @return
     */
    private native boolean sendData(byte[] data, int len, int type, long tms);

}

自定义的对象池关键接口和实现:

public interface ArrayPool {

    byte[] get(int len);

    void put(byte[] data);

}

public class LruArrayPool implements ArrayPool {

    private static final String TAG = "ArrayPool";

    public static final int ARRAY_POOL_SIZE_BYTES = 4 * 1024 * 1024;
    private int maxSize;

    private LruMap lruMap = new LruMap<>();

    // key:byte[]长度  value:个数!
    private TreeArray map = new TreeArray();
    private int currentSize;

    public LruArrayPool() {
        this(ARRAY_POOL_SIZE_BYTES);
    }

    public LruArrayPool(int maxSize) {
        this.maxSize = maxSize;
        currentSize = 0;
    }

    /**
     * @param len
     * @return
     */
    @Override
    public synchronized byte[] get(int len) {
        //获得等于或大于比len大同时最接近len的key
        int key = map.ceilingKey(len);
        if (key != 0) {
            byte[] bytes = lruMap.get(key);
            if (bytes != null) {
                currentSize -= bytes.length;
                //计数器-1
                decrementArrayOfSize(key);
                return bytes;
            }
        }
        Log.i(TAG, "get: new");
        return new byte[len];
    }

    @Override
    public synchronized void put(byte[] data) {
        if (data == null || data.length == 0 || data.length > maxSize) return;
        int length = data.length;
        lruMap.put(length, data);
        //value :个数
        int current = map.get(length);
        //计数器+1
        map.put(length, current == 0 ? 1 : current + 1);

        currentSize += length;
        evict();
    }

    private void evict() {
        while (currentSize > maxSize) {
            byte[] evicted = lruMap.removeLast();
            currentSize -= evicted.length;
            //计数器-1
            decrementArrayOfSize(evicted.length);
        }
    }

    private void decrementArrayOfSize(int key) {
        int current = map.get(key);
        if (current == 1) {
            map.delete(key);
        } else {
            map.put(key, current - 1);
        }
    }

    @Override
    public String toString() {
        return "ArrayPoolCustom{" +
                "lruMap=" + lruMap +
                '}';
    }
}

public class LruMap {
    private LinkedEntry head = new LinkedEntry<>(0);
    private final SparseArray> keyToEntry = new SparseArray<>();

    public void put(int key, V value) {
        LinkedEntry entry = keyToEntry.get(key);
        if (entry == null) {
            entry = new LinkedEntry<>(key);
            keyToEntry.put(key, entry);
        }
        makeHead(entry);
        entry.add(value);
    }

    public V get(int key) {
        LinkedEntry entry = keyToEntry.get(key);
        if (entry != null) {
            makeHead(entry);
            return entry.removeLast();
        }
        return null;
    }


    private void makeHead(LinkedEntry entry) {
        // 把自己前面和后面的连接到一起
        entry.prev.next = entry.next;
        entry.next.prev = entry.prev;
        //把自己放到 head 后面第一个
        entry.prev = head;
        entry.next = head.next;

        entry.next.prev = entry;
        entry.prev.next = entry;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder("GroupedLinkedMap( ");
        LinkedEntry current = head.next;
        boolean hadAtLeastOneItem = false;
        while (!current.equals(head)) {
            hadAtLeastOneItem = true;
            sb.append('{').append(current.key).append(':').append(current.size()).append("}, ");
            current = current.next;
        }
        if (hadAtLeastOneItem) {
            sb.delete(sb.length() - 2, sb.length());
        }
        return sb.append(" )").toString();
    }

    public V removeLast() {
        LinkedEntry last = head.prev;

        while (!last.equals(head)) {
            //移除链表最末尾的LinkedEntry中的一个byte数组
            V removed = last.removeLast();
            //如果最后一个LinkedEntry没有byte数组缓存,则将最后一个LinkedEntry移除然后循环
            if (removed != null) {
                return removed;
            } else {
                last.prev.next = last.next;
                last.next.prev = last.prev;
                keyToEntry.remove(last.key);
            }
            last = last.prev;
        }
        return null;
    }

    private static class LinkedEntry {
        private final int key;
        private List values;
        LinkedEntry next;
        LinkedEntry prev;

        LinkedEntry(int key) {
            this.key = key;
            prev = next = this;
        }

        @Nullable
        public V removeLast() {
            final int valueSize = size();
            return valueSize > 0 ? values.remove(valueSize - 1) : null;
        }

        public int size() {
            return values != null ? values.size() : 0;
        }

        public void add(V value) {
            if (values == null) {
                values = new ArrayList<>();
            }
            values.add(value);
        }
    }
}

public class TreeArray {

    public static final int[] INT = new int[0];

    private int[] mKeys;
    private int[] mValues;
    private int mSize;

    public TreeArray() {
        this(10);
    }


    public TreeArray(int initialCapacity) {
        if (initialCapacity == 0) {
            mKeys = INT;
            mValues = INT;
        } else {
            mKeys = new int[initialCapacity];
            mValues = new int[mKeys.length];
        }
        mSize = 0;
    }

    @Override
    public TreeArray clone() {
        TreeArray clone = null;
        try {
            clone = (TreeArray) super.clone();
            clone.mKeys = mKeys.clone();
            clone.mValues = mValues.clone();
        } catch (CloneNotSupportedException cnse) {
        }
        return clone;
    }


    public int get(int key) {
        return get(key, 0);
    }

    static int binarySearch(int[] array, int size, int value) {
        int lo = 0;
        int hi = size - 1;

        while (lo <= hi) {
            final int mid = (lo + hi) >>> 1;
            final int midVal = array[mid];

            if (midVal < value) {
                lo = mid + 1;
            } else if (midVal > value) {
                hi = mid - 1;
            } else {
                return mid;
            }
        }
        return ~lo;
    }


    public int get(int key, int valueIfKeyNotFound) {
        int i = binarySearch(mKeys, mSize, key);

        if (i < 0) {
            return valueIfKeyNotFound;
        } else {
            return mValues[i];
        }
    }


    public void delete(int key) {
        int i = binarySearch(mKeys, mSize, key);

        if (i >= 0) {
            removeAt(i);
        }
    }


    public void removeAt(int index) {
        System.arraycopy(mKeys, index + 1, mKeys, index, mSize - (index + 1));
        System.arraycopy(mValues, index + 1, mValues, index, mSize - (index + 1));
        mSize--;
    }

    public static int growSize(int currentSize) {
        return currentSize <= 4 ? 8 : currentSize * 2;
    }

    public static int[] insert(int[] array, int currentSize, int index, int element) {
        assert currentSize <= array.length;

        if (currentSize + 1 <= array.length) {
            System.arraycopy(array, index, array, index + 1, currentSize - index);
            array[index] = element;
            return array;
        }

        int[] newArray = new int[growSize(currentSize)];
        System.arraycopy(array, 0, newArray, 0, index);
        newArray[index] = element;
        System.arraycopy(array, index, newArray, index + 1, array.length - index);
        return newArray;
    }

    public void put(int key, int value) {
        int i = binarySearch(mKeys, mSize, key);

        if (i >= 0) {
            mValues[i] = value;
        } else {
            i = ~i;

            mKeys = insert(mKeys, mSize, i, key);
            mValues = insert(mValues, mSize, i, value);
            mSize++;
        }
    }


    public int size() {
        return mSize;
    }


    public int keyAt(int index) {
        return mKeys[index];
    }


    public int valueAt(int index) {
        return mValues[index];
    }


    @Override
    public String toString() {
        if (size() <= 0) {
            return "{}";
        }

        StringBuilder buffer = new StringBuilder(mSize * 28);
        buffer.append('{');
        for (int i = 0; i < mSize; i++) {
            if (i > 0) {
                buffer.append(", ");
            }
            int key = keyAt(i);
            buffer.append(key);
            buffer.append('=');
            int value = valueAt(i);
            buffer.append(value);
        }
        buffer.append('}');
        return buffer.toString();
    }


    public int ceilingKey(int key) {
        int r = -1;
        for (int mKey : mKeys) {
            if (mKey >= key) {
                return mKey;
            }
        }
        return r;
    }
}

自定义线程池:

public class LiveTaskManager {


    private static volatile LiveTaskManager instance;

    private ThreadPoolExecutor THREAD_POOL_EXECUTOR;


    private LiveTaskManager() {
        THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(1, 1,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue());
    }

    public static LiveTaskManager getInstance() {
        if (instance == null) {
            synchronized (LiveTaskManager.class) {
                if (instance == null) {
                    instance = new LiveTaskManager();
                }
            }
        }
        return instance;
    }


    public void execute(Runnable runnable) {
        THREAD_POOL_EXECUTOR.execute(runnable);
    }

}

在MainActivity中使用如下:

public class MainActivity extends AppCompatActivity {

    private ScreenLive mScreenLive;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.RECORD_AUDIO}, 10);

        Handler handler = new Handler();
//        Message message1 = new Message();
        Message message = handler.obtainMessage(); // what = 1, what =2 ,what = 2
//        message.what = 2;
        handler.sendMessage(message);


    }



    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == 100) {
            mScreenLive.onActivityResult(requestCode, resultCode, data);
        }
    }

    public void startLive(View view) {
        mScreenLive = new ScreenLive();
        mScreenLive.startLive(this,
                "rtmp://sendtc3a.douyu.com/live/6918788rd3WwQNSV?wsSecret=e4661e772c05bc58c8f08b13eb6df517&wsTime=6319e048&wsSeek=off&wm=0&tw=0&roirecognition=0&record=flv&origin=tct");
    }

    public void stopLive(View view) {
        mScreenLive.stoptLive();
    }

}

布局文件activity_main.xml




    

你可能感兴趣的:(java)