Android RTMP录频直播二(录屏H264视频编码)

一、录屏H264编码简单流程
录屏视频H264编码.png
1. H264编码配置类
public class VideoUtils {
    // 360*640(1000_000) 540*960(高清 1500_000) 720*1280(超清 1800_000) 1080*1920(蓝光 2500_000)
    // MIMETYPE_VIDEO_AVC   MIMETYPE_VIDEO_MPEG4
    public static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
    public static final int DECODE_HEIGHT = 1080;
    public static final int DECODE_WIDTH = 1920;
    public static final int BIT_RATE = 2500_000; // 码率
    // 码率控制方式 CQP(恒定质量),CRF(恒定码率),VBR(平均码率)。 VBR好一些,不会出现花点
    public static final int BITRATE_MODE = MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR;
    public static final int FRAME_RATE = 30; // I帧刷新频率
    public static final int I_FRAME_INTERVAL = 1; // 每秒刷新
}
2. 录屏H264编码代码
/**
 * H264视频编码
 * MediaProjection(录屏数据) + MediaCodec H264编码
 */
public class MediaVideoEncoder implements Runnable {
    private static final String MIME_TYPE = VideoUtils.MIME_TYPE;
    private AtomicBoolean isStart = new AtomicBoolean(false);
    private MediaProjection mediaProjection;
    private MediaCodec mediaCodec;
    private Surface surface;
    // 录频相关
    private VirtualDisplay virtualDisplay;
    private byte[] sps;
    private byte[] pps;
    private OnFrameListener onFrameListener;
    private int timeoutUs = 30;


    public MediaVideoEncoder(MediaProjection mediaProjection) {
        this.mediaProjection = mediaProjection;
        initMediaCode();
    }

    private void initMediaCode() {
        try {
            MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, VideoUtils.DECODE_WIDTH, VideoUtils.DECODE_HEIGHT);
            // 码率控制方式 CQP(恒定质量),CRF(恒定码率),VBR(平均码率)。 VBR好一些,不会出现花点
            format.setInteger(MediaFormat.KEY_BITRATE_MODE, VideoUtils.BITRATE_MODE);
            format.setInteger(MediaFormat.KEY_BIT_RATE, VideoUtils.BIT_RATE);
            format.setInteger(MediaFormat.KEY_FRAME_RATE, VideoUtils.FRAME_RATE);
            format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, VideoUtils.I_FRAME_INTERVAL);
            format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
            mediaCodec = MediaCodec.createEncoderByType(MIME_TYPE);
            mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
            surface = mediaCodec.createInputSurface();
            // 录频关联MediaCodec
            virtualDisplay = mediaProjection.createVirtualDisplay(
                    "VirtualDisplay", VideoUtils.DECODE_WIDTH, VideoUtils.DECODE_HEIGHT, 1,
                    DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
                    surface, null, null);
        } catch (Exception e) {
            e.printStackTrace();
            isStart.set(false);
        }
    }

    public void start() {
        if (isStart.compareAndSet(false, true)) {
            Executors.newSingleThreadExecutor().execute(this);
        }
    }

    public void stop() {
        isStart.set(false);
    }

    @Override
    public void run() {
        if (mediaCodec == null) {
            Log.e("TAG", "initMediaCode 失败...");
            return;
        }
        Log.e("TAG", "start h264 encode...");
        mediaCodec.start();
        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
        // 计算PTS
        // DTS表示解码时间戳,在什么时候解码这一帧的数据.
        // PTS表示显示时间戳 ,在什么时候显示这一帧。
        long pts = 0;
        while (isStart.get()) {
            int index = mediaCodec.dequeueOutputBuffer(info, timeoutUs);

            if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                // sps pps
                MediaFormat outputFormat = mediaCodec.getOutputFormat();
                ByteBuffer csd0 = outputFormat.getByteBuffer("csd-0");
                ByteBuffer csd1 = outputFormat.getByteBuffer("csd-1");
                if (csd1 != null) {
                    sps = new byte[csd0.remaining()];
                    pps = new byte[csd1.remaining()];
                    csd0.get(sps, 0, csd0.remaining());
                    csd1.get(pps, 0, csd1.remaining());
                }
            } else if (index >= 0) {
                // 其它帧
                ByteBuffer buffer = mediaCodec.getOutputBuffer(index);
                buffer.position(info.offset);
                buffer.limit(info.size);
                if (pts == 0) {
                    pts = info.presentationTimeUs;
                }
                info.presentationTimeUs -= pts;
                // 处理帧数据
                frameData(info, buffer, info.presentationTimeUs);
                mediaCodec.releaseOutputBuffer(index, false);
            }
        }
        // 回收
        release();
        Log.e("TAG", "stop h264 encode...");
    }

    // 回收
    private void release() {
        if (mediaCodec != null) {
            mediaCodec.stop();
            mediaCodec.release();
        }
        if (virtualDisplay != null) {
            virtualDisplay.release();
        }
        if (mediaProjection != null) {
            mediaProjection.stop();
        }
        mediaCodec = null;
        virtualDisplay = null;
        mediaProjection = null;
        onFrameListener = null;
        sps = null;
        pps = null;
    }


    private void frameData(MediaCodec.BufferInfo info, ByteBuffer buffer, long time) {
        // I B P及其它帧
        byte[] data = new byte[info.size];
        buffer.get(data);
        int type = info.flags;
        // 1. 如果是I帧就发sps pps
        if (type == MediaCodec.BUFFER_FLAG_KEY_FRAME) {
            // 1. 发个sps pps
            if (sps != null && pps != null) {
                if (onFrameListener != null) {
                    onFrameListener.onSpsPpsListener(type, sps, sps.length, pps, pps.length);
                }
            }
        }
        // 2. 发帧数据
        if (onFrameListener != null) {
            onFrameListener.onFameListener(type, data, data.length, time);
        }
    }

    public interface OnFrameListener {
        //  sps pps 回调
        void onSpsPpsListener(int type, byte[] sps, int spsLen, byte[] pps, int ppsLen);

        // I B P 帧回调 type = 1 时是I帧
        void onFameListener(int type, byte[] data, int len, long time);
    }

    public void setOnFrameListener(OnFrameListener onFrameListener) {
        this.onFrameListener = onFrameListener;
    }
}
  1. 使用测试类
// 录屏推流类
public class RtmpPushActivity extends AppActivity implements ProjectionObserver, XPermissionListener {
    private ProjectionUtils mProjectionUtils;
    private MediaVideoEncoder mVideoEncoder;

    @Override
    protected void initTitle(DefTitleBar titleBar) {
        titleBar.setTitle("RtmpPush");
    }

    @Override
    public int getContentLayout() {
        return R.layout.activity_push_rtmp;
    }

    @Override
    public void initData(Bundle savedInstanceState) {
        // 录屏帮助类
        mProjectionUtils = new ProjectionUtils();
        // 创建录屏MediaProjection类成功回调
        ProjectionObservable.getInstance().register(this);
    }

    // 1. 开启录屏申请
    public void start(View view) {
        // 1. 方法调用后,到->onActivityResult
        mProjectionUtils.start(this);
    }

    // 2.处理录屏申请
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
    // 2. 处理录屏申请 -> 到onChange(MediaProjection mediaProjection)
        mProjectionUtils.onActivityResult(requestCode, resultCode, data);
    }

    // 3. 录屏创建MediaProjection成功回调
    @Override
    public void onChange(MediaProjection mediaProjection) {
        if (mVideoEncoder != null){
        mVideoEncoder.stop();
    }
    // 录屏H264编码
        mVideoEncoder = new MediaVideoEncoder(mediaProjection);
    // 开启录屏编码
    mVideoEncoder.start();
    }
    // 4.停止录频
    public void stop(View view) {
        if (mProjectionUtils != null) {
            mProjectionUtils.stop();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mProjectionUtils != null) {
            mProjectionUtils.stop();
        }
    if (mVideoEncoder != null){
        mVideoEncoder.stop();
    }
        // 反注册录屏回调
        ProjectionObservable.getInstance().unregister(this);
    }

}
4.补充说明
  1. MediaProjection 这个类是屏幕服务创建的。具体请看Android RTMP录频直播一(录屏)。
  2. MediaCodec进行H264编码时,sps pps只会编一次,得保存起来,在每次I帧发送前,先发sps pps,否则播放端解码时拿不到sps pps,播放不了视频。

你可能感兴趣的:(Android RTMP录频直播二(录屏H264视频编码))