Android视频采集实时推送RTP/RTSP/RTMP

因为在工作中,接触到了视频相关的开发工作;同时,大多数android处理音视频多半都是有C++工程师提供处理库,所以,在这里记录一下我自己在工作中遇到的问题。

主要功能:
采集Android摄像头数据,实时编码H264,发送至指定地址(RTP/RTSP/RTMP等等,还有很多其他封装格式,可自行研究)。

	本文使用了javacv来处理音视频数据,javacv是一套java封装的jni库,可以适用于本地多媒体(音视频)调用以及音视频,图片等文件后期操作(图片修改,音视频解码剪辑等等功能)。
	javacv官方github维护地址:https://github.com/bytedeco/javacv

实现步骤:

  1. Android视频采集
  2. 视频编码为H264
  3. 建立管道流
    PipedInputStream与PipedOutputStream建立连接,将PipedInputStream作为视频源,传给javacv。
    PipedInputStream的大小默认为1024,大小视情况来定。(javacv内部读取视频数据是4096)
 /**
     * 开启编码与javacv录制的通道
     */
    private Runnable sendRunnable = new Runnable() {
        @Override
        public void run() {
            //管道流,编码数据与javacv录制建立
            PipedInputStream pipedInputStream = null;
            try {
                pipedInputStream = new PipedInputStream(1024 * 4);
                pipedOutputStream = new PipedOutputStream(pipedInputStream);
            } catch (IOException e) {
                e.printStackTrace();
            }
            String mTag;
            if (m_CameraId == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                mTag = "front";
            } else {
                mTag = "back";
            }
            javaCvHelper = new JavaCvHelper(pipedInputStream, m_outPath, CameraHelper.this, mTag);
        }
    };
  1. 在编码输出位置,将H264数据写入管道流
    在这个地方需要注意一下。javacv会默认发送第一帧信息帧(SPS+PPS),需要在这里将信息帧保留,并不发送第一帧,在后续的I帧前,发送信息帧(方便播放端快速解码显示)。
while (outputBufferIndex >= 0) {
                ByteBuffer outputBuffer = mEncoder.getOutputBuffer(outputBufferIndex);
                byte[] outData = new byte[bufferInfo.size];
                outputBuffer.get(outData);
                //存储SPS/PPS
                if (bufferInfo.flags == MediaCodec.BUFFER_FLAG_CODEC_CONFIG) {
                    configbyte = outData;
                }
                //是关键帧,发送sps\pps
                if (bufferInfo.flags == MediaCodec.BUFFER_FLAG_KEY_FRAME) {
                    LogUtil.e(TAG, "KEY_FRAME !");
                    mCount = 0;
                    if (!isFirstFrame) {
                        sendData(configbyte);
                    }
                }
                mCount++;
//                LogUtil.e(TAG, "_FRAME " + mCount);
                if (!isFirstFrame) {
                    sendData(outData);
                }
                isFirstFrame = false;
                mEncoder.releaseOutputBuffer(outputBufferIndex, false);
                outputBufferIndex = mEncoder.dequeueOutputBuffer(bufferInfo, 12000);
            }
  /**
     * 发送数据至javacv
     *
     * @param outData
     */
    private void sendData(final byte[] outData) {
        writeExecutor.execute(new Runnable() {
            @Override
            public void run() {
                if (pipedOutputStream != null) {
                    try {
                        pipedOutputStream.write(outData);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }
  1. javacv的处理

FFmpegFrameGrabberPlus是重写的FFmpegFrameGrabber;
修改了当视频源为InputStream是,注释了inputStream = new BufferedInputStream(inputStream);因为在测试过程中发现,BufferedInputStream会占用很多的缓存,导致内存暴涨。注释掉,就没什么问题了。

FFmpegFrameRecorderPlus是重写的FFmpegFrameRecorder;
增加了在recordPacket时,pts的同步计算问题。持续使用时间达2小时左右,可能出现视频播放延时60s,经分析,是pts和dts导致的。

如果是rtp地址, recorder.setFormat(“rtp”)即可;
如果是rtmp地址, recorder.setFormat(“flv”)即可;
如果是rtsp地址, recorder.setFormat(“rtsp”)即可;

  /**
     * 数据封装与发送
     *
     * @throws FrameGrabber.Exception
     * @throws FFmpegFrameRecorder.Exception
     */
    private void recordStream() throws FrameGrabber.Exception, FFmpegFrameRecorderPlus.Exception {
        this.isRunning = true;
        //数据采集
        FFmpegFrameGrabberPlus grabber = new FFmpegFrameGrabberPlus(inStream);
        grabber.setVideoOption("vcodec", "copy");
        grabber.start();
        LogUtil.d(TAG, "视频宽" + grabber.getImageWidth());
        LogUtil.d(TAG, "视频高" + grabber.getImageHeight());
        LogUtil.d(TAG, "视频帧率:" + grabber.getVideoFrameRate());
        LogUtil.d(TAG, "视频比特率:" + grabber.getVideoBitrate());

        //数据录制(推送)
        FFmpegFrameRecorderPlus recorder = new FFmpegFrameRecorderPlus(outUrl, grabber.getImageWidth(), grabber.getImageHeight(), 0);
        recorder.setFormat("rtp");
        recorder.setVideoOption("preset", "ultrafast");
        recorder.setVideoOption("tune", "zerolatency");

        recorder.start(grabber.getFormatContext());
        AVPacket avPacket = null;
        int error_times = 0;
        while (isRunning && error_times < 1) {
            avPacket = grabber.grabPacket();
            if (avPacket == null) {
                LogUtil.d(TAG, "- Null AVPacket -");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                continue;
            }
            if (avPacket.flags() == avcodec.AV_PKT_FLAG_KEY) {
                LogUtil.d(TAG, "- AVPacket -");
            }
            pts = System.nanoTime() / 1000;
            avPacket.pts(pts);
            avPacket.dts(pts);
            error_times += (recorder.recordPacket(avPacket) ? 0 : 1);
            avcodec.av_free_packet(avPacket);
            avcodec.av_packet_unref(avPacket);
            avutil.av_freep(avPacket);
        }
        recorder.stop();
        grabber.stop();
        mCloseLisener.onJavacvClosed(mTag);
        LogUtil.d(TAG, "- Stop AVPacket -");
    }
  1. 关于javacv内存释放的问题
    avcodec.av_free_packet(avPacket);
    avcodec.av_packet_unref(avPacket);
    avutil.av_freep(avPacket);。

    因为有些时候只调用avcodec.av_free_packet(avPacket);是起作用的。都调用,也没什么影响。可以自己选择。

以上是我在工作中碰到的一些问题和解决方案,记录一下。

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值