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

时间:2025-02-16 09:07:37

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

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

	本文使用了javacv来处理音视频数据,javacv是一套java封装的jni库,可以适用于本地多媒体(音视频)调用以及音视频,图片等文件后期操作(图片修改,音视频解码剪辑等等功能)。
	javacv官方github维护地址:/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) {
                ();
            }
            String mTag;
            if (m_CameraId == .CAMERA_FACING_FRONT) {
                mTag = "front";
            } else {
                mTag = "back";
            }
            javaCvHelper = new JavaCvHelper(pipedInputStream, m_outPath, , mTag);
        }
    };
  1. 在编码输出位置,将H264数据写入管道流
    在这个地方需要注意一下。javacv会默认发送第一帧信息帧(SPS+PPS),需要在这里将信息帧保留,并不发送第一帧,在后续的I帧前,发送信息帧(方便播放端快速解码显示)。
while (outputBufferIndex >= 0) {
                ByteBuffer outputBuffer = (outputBufferIndex);
                byte[] outData = new byte[];
                (outData);
                //存储SPS/PPS
                if ( == MediaCodec.BUFFER_FLAG_CODEC_CONFIG) {
                    configbyte = outData;
                }
                //是关键帧,发送sps\pps
                if ( == MediaCodec.BUFFER_FLAG_KEY_FRAME) {
                    (TAG, "KEY_FRAME !");
                    mCount = 0;
                    if (!isFirstFrame) {
                        sendData(configbyte);
                    }
                }
                mCount++;
//                (TAG, "_FRAME " + mCount);
                if (!isFirstFrame) {
                    sendData(outData);
                }
                isFirstFrame = false;
                (outputBufferIndex, false);
                outputBufferIndex = (bufferInfo, 12000);
            }
  /**
     * 发送数据至javacv
     *
     * @param outData
     */
    private void sendData(final byte[] outData) {
        (new Runnable() {
            @Override
            public void run() {
                if (pipedOutputStream != null) {
                    try {
                        (outData);
                    } catch (IOException e) {
                        ();
                    }
                }
            }
        });
    }
  1. javacv的处理

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

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

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

  /**
     * 数据封装与发送
     *
     * @throws 
     * @throws 
     */
    private void recordStream() throws ,  {
         = true;
        //数据采集
        FFmpegFrameGrabberPlus grabber = new FFmpegFrameGrabberPlus(inStream);
        ("vcodec", "copy");
        ();
        (TAG, "视频宽" + ());
        (TAG, "视频高" + ());
        (TAG, "视频帧率:" + ());
        (TAG, "视频比特率:" + ());

        //数据录制(推送)
        FFmpegFrameRecorderPlus recorder = new FFmpegFrameRecorderPlus(outUrl, (), (), 0);
        ("rtp");
        ("preset", "ultrafast");
        ("tune", "zerolatency");

        (());
        AVPacket avPacket = null;
        int error_times = 0;
        while (isRunning && error_times < 1) {
            avPacket = ();
            if (avPacket == null) {
                (TAG, "- Null AVPacket -");
                try {
                    (100);
                } catch (InterruptedException e) {
                    ();
                }
                continue;
            }
            if (() == avcodec.AV_PKT_FLAG_KEY) {
                (TAG, "- AVPacket -");
            }
            pts = () / 1000;
            (pts);
            (pts);
            error_times += ((avPacket) ? 0 : 1);
            avcodec.av_free_packet(avPacket);
            avcodec.av_packet_unref(avPacket);
            avutil.av_freep(avPacket);
        }
        ();
        ();
        (mTag);
        (TAG, "- Stop AVPacket -");
    }
  1. 关于javacv内存释放的问题
    avcodec.av_free_packet(avPacket);
    avcodec.av_packet_unref(avPacket);
    avutil.av_freep(avPacket);。

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

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