• Android平台实现屏幕采集并推送至RTMP服务器


    随着无纸化、智慧教室等场景的普及,好多企业或者开发者开始寻求更高效稳定低延迟的RTMP同屏方案,本文以大牛直播SDK(Github)的同屏demo(对应工程:SmartServicePublisherV2)为例,介绍下如何采集编码推送RTMP数据到流媒体服务器。

    系统要求:Android 5.0及以上系统。

    废话不多说,上代码:

    获取screen windows宽高,如需缩放,按照一定的比例缩放即可:

        private void createScreenEnvironment() {
    
            sreenWindowWidth = mWindowManager.getDefaultDisplay().getWidth();
            screenWindowHeight = mWindowManager.getDefaultDisplay().getHeight();
    
            Log.i(TAG, "screenWindowWidth: " + sreenWindowWidth + ",screenWindowHeight: "
                    + screenWindowHeight);
    
            if (sreenWindowWidth > 800)
            {
                if (screenResolution == SCREEN_RESOLUTION_STANDARD)
                {
                    scale_rate = SCALE_RATE_HALF;
                    sreenWindowWidth = align(sreenWindowWidth / 2, 16);
                    screenWindowHeight = align(screenWindowHeight / 2, 16);
                }
                else if(screenResolution == SCREEN_RESOLUTION_LOW)
                {
                    scale_rate = SCALE_RATE_TWO_FIFTHS;
                    sreenWindowWidth = align(sreenWindowWidth * 2 / 5, 16);
    
                }
            }
    
            Log.i(TAG, "After adjust mWindowWidth: " + sreenWindowWidth + ", mWindowHeight: " + screenWindowHeight);
    
            int pf = mWindowManager.getDefaultDisplay().getPixelFormat();
            Log.i(TAG, "display format:" + pf);
    
            DisplayMetrics displayMetrics = new DisplayMetrics();
            mWindowManager.getDefaultDisplay().getMetrics(displayMetrics);
            mScreenDensity = displayMetrics.densityDpi;
    
            mImageReader = ImageReader.newInstance(sreenWindowWidth,
                    screenWindowHeight, 0x1, 6);
    
            mMediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
        }

    获取到image数据后,传递到processScreenImage()处理:

       private void setupMediaProjection() {
            mMediaProjection = mMediaProjectionManager.getMediaProjection(
                    MainActivity.mResultCode, MainActivity.mResultData);
        }
    
    
        private void setupVirtualDisplay() {
            mVirtualDisplay = mMediaProjection.createVirtualDisplay(
                    "ScreenCapture", sreenWindowWidth, screenWindowHeight,
                    mScreenDensity,
                    DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                    mImageReader.getSurface(), null, null);
    
            mImageReader.setOnImageAvailableListener(
                    new ImageReader.OnImageAvailableListener() {
                        @Override
                        public void onImageAvailable(ImageReader reader) {
                            Image image = mImageReader.acquireLatestImage();
                            if (image != null) {
                                processScreenImage(image);
                                //image.close();
                            }
                        }
                    }, null);
        }

    数据放到image list里面:

        private void  pushImage(Image image)
        {
            if ( null ==image )
                return;
    
            final int image_list_max_count = 1;
    
            LinkedList<Image> close_images = null;
    
            synchronized (image_list_lock)
            {
                if (image_list.size() > image_list_max_count )
                {
                    close_images = new LinkedList();
    
                    while ( image_list.size() > image_list_max_count)
                    {
                        close_images.add(image_list.poll());
                    }
                }
    
                image_list.add(image);
            }
    
            if ( close_images != null )
            {
                while( !close_images.isEmpty() ) {
    
                    Image i = close_images.poll();
                    if ( i != null )
                    {
                        i.close();
    
                        //Log.i("PushImage", "drop image");
                    }
                }
            }
        }

    调用大牛直播SDK的RTMP初始化和参数设置接口:

            libPublisher = new SmartPublisherJniV2();    
    
            private void InitAndSetConfig() {
            //开始要不要采集音频或视频,请自行设置
            publisherHandle = libPublisher.SmartPublisherOpen(this.getApplicationContext(),
                    audio_opt, video_opt, sreenWindowWidth,
                    screenWindowHeight);
    
            if ( publisherHandle == 0 )
            {
                return;
            }
    
            Log.i(TAG, "publisherHandle=" + publisherHandle);
    
            libPublisher.SetSmartPublisherEventCallbackV2(publisherHandle, new EventHandeV2());
    
            if(videoEncodeType == 1)
            {
                int h264HWKbps = setHardwareEncoderKbps(true, sreenWindowWidth,
                        screenWindowHeight);
    
                Log.i(TAG, "h264HWKbps: " + h264HWKbps);
    
                int isSupportH264HWEncoder = libPublisher
                        .SetSmartPublisherVideoHWEncoder(publisherHandle, h264HWKbps);
    
                if (isSupportH264HWEncoder == 0) {
                    Log.i(TAG, "Great, it supports h.264 hardware encoder!");
                }
            }
            else if (videoEncodeType == 2)
            {
                int hevcHWKbps = setHardwareEncoderKbps(false, sreenWindowWidth,
                        screenWindowHeight);
    
                Log.i(TAG, "hevcHWKbps: " + hevcHWKbps);
    
                int isSupportHevcHWEncoder = libPublisher
                        .SetSmartPublisherVideoHevcHWEncoder(publisherHandle, hevcHWKbps);
    
                if (isSupportHevcHWEncoder == 0) {
                    Log.i(TAG, "Great, it supports hevc hardware encoder!");
                }
            }
    
            if(is_sw_vbr_mode)
            {
                int is_enable_vbr = 1;
                int video_quality = CalVideoQuality(sreenWindowWidth,
                        screenWindowHeight, true);
                int vbr_max_bitrate = CalVbrMaxKBitRate(sreenWindowWidth,
                        screenWindowHeight);
    
                libPublisher.SmartPublisherSetSwVBRMode(publisherHandle, is_enable_vbr, video_quality, vbr_max_bitrate);
            }
    
            //音频相关可以参考SmartPublisher工程
    		/*
    		if (!is_speex)
    		{
    			// set AAC encoder
    			libPublisher.SmartPublisherSetAudioCodecType(publisherHandle, 1);
    		}
    		else
    		{
    			// set Speex encoder
    			libPublisher.SmartPublisherSetAudioCodecType(publisherHandle, 2);
    			libPublisher.SmartPublisherSetSpeexEncoderQuality(publisherHandle, 8);
    		}
    
    		libPublisher.SmartPublisherSetNoiseSuppression(publisherHandle, is_noise_suppression ? 1
    				: 0);
    
    		libPublisher.SmartPublisherSetAGC(publisherHandle, is_agc ? 1 : 0);
    		*/
    
            // libPublisher.SmartPublisherSetClippingMode(publisherHandle, 0);
    
            //libPublisher.SmartPublisherSetSWVideoEncoderProfile(publisherHandle, sw_video_encoder_profile);
    
            //libPublisher.SmartPublisherSetSWVideoEncoderSpeed(publisherHandle, sw_video_encoder_speed);
    
            // libPublisher.SetRtmpPublishingType(publisherHandle, 0);
    
             libPublisher.SmartPublisherSetFPS(publisherHandle, 18);    //帧率可调
    
             libPublisher.SmartPublisherSetGopInterval(publisherHandle, 18*3);
    
             //libPublisher.SmartPublisherSetSWVideoBitRate(publisherHandle, 1200, 2400); //针对软编码有效,一般最大码率是平均码率的二倍
    
             libPublisher.SmartPublisherSetSWVideoEncoderSpeed(publisherHandle, 3);
    
             //libPublisher.SmartPublisherSaveImageFlag(publisherHandle, 1);
    
        }

    初始化、参数设置后,设置RTMP推送的URL,并调用SartPublisher()接口,开始推送:

            //如果同时推送和录像,设置一次就可以
            InitAndSetConfig();
    
            if ( publisherHandle == 0 )
            {
                stopScreenCapture();
    
                return;
            }
    
            if(push_type == PUSH_TYPE_RTMP)
            {
                String publishURL = intent.getStringExtra("PUBLISHURL");
    
                Log.i(TAG, "publishURL: " + publishURL);
    
                if (libPublisher.SmartPublisherSetURL(publisherHandle, publishURL) != 0) {
                    stopScreenCapture();
    
                    Log.e(TAG, "Failed to set publish stream URL..");
    
                    if (publisherHandle != 0) {
                        if (libPublisher != null) {
                            libPublisher.SmartPublisherClose(publisherHandle);
                            publisherHandle = 0;
                        }
                    }
    
                    return;
                }
            }
    
            //启动传递数据线程
            post_data_thread = new Thread(new DataRunnable());
            Log.i(TAG, "new post_data_thread..");
    
            is_post_data_thread_alive = true;
            post_data_thread.start();
      int startRet = libPublisher.SmartPublisherStartPublisher(publisherHandle);
    
                if (startRet != 0) {
                    isPushingRtmp = false;
    
                    Log.e(TAG, "Failed to start push rtmp stream..");
                    return;
                }
    
      //如果同时推送和录像,Audio启动一次就可以了
      CheckInitAudioRecorder();

    开始推送后,传递数据到底层SDK:

        public class DataRunnable implements Runnable{
            private final static String TAG = "DataRunnable==> ";
    
            @Override
            public void run() {
                // TODO Auto-generated method stub
                Log.i(TAG, "post data thread is running..");
    
                ByteBuffer last_buffer = null;
    
                Image last_image = null;
    
                long last_post_time = System.currentTimeMillis();
    
                while (is_post_data_thread_alive)
                {
                    boolean is_skip = false;
    
                    /*
                    synchronized (data_list_lock)
                    {
                        if ( data_list.isEmpty())
                        {
                            if((System.currentTimeMillis() - last_post_time) > frame_added_interval_setting)
                            {
                                if(last_buffer != null)
                                {
                                    Log.i(TAG, "补帧中..");
                                }
                                else
                                {
                                    is_skip = true;
                                }
                            }
                            else
                            {
                               is_skip = true;
                            }
                        }
                        else
                        {
                            last_buffer = data_list.get(0);
    
                            data_list.remove(0);
                        }
                    }
                    */
    
                    Image new_image = popImage();
    
                    if ( new_image == null )
                    {
                        if((System.currentTimeMillis() - last_post_time) > frame_added_interval_setting)
                        {
                            if(last_image != null)
                            {
                                Log.i(TAG, "补帧中..");
                            }
                            else
                            {
                                is_skip = true;
                            }
                        }
                        else
                        {
                            is_skip = true;
                        }
                    }
                    else
                    {
                        if ( last_image != null )
                        {
                            last_image.close();
                        }
    
                        last_image = new_image;
                    }
    
                    if( is_skip )
                    {
                        // Log.i("OnScreenImage", "is_skip");
    
                        try {
                            Thread.sleep(5);   //休眠5ms
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    else
                    {
                        //if( last_buffer != null && publisherHandle != 0 && (isPushing || isRecording || isRTSPPublisherRunning) )
                        if( last_image != null && publisherHandle != 0 && (isPushingRtmp || isRecording || isRTSPPublisherRunning) )
                        {
                            long post_begin_time = System.currentTimeMillis();
    
                            final Image.Plane[] planes = last_image.getPlanes();
    
                            if ( planes != null && planes.length > 0 )
                            {
                                libPublisher.SmartPublisherOnCaptureVideoRGBAData(publisherHandle, planes[0].getBuffer(), planes[0].getRowStride(),
                                        last_image.getWidth(), last_image.getHeight());
                            }
    
                            last_post_time = System.currentTimeMillis();
    
                            long post_cost_time = last_post_time - post_begin_time;
    
                            if ( post_cost_time >=0 && post_cost_time < 10 )
                            {
                                try {
                                    Thread.sleep(10-post_cost_time);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                            }
    
                            /*
                            libPublisher.SmartPublisherOnCaptureVideoRGBAData(publisherHandle, last_buffer, row_stride_,
                                    width_, height_);
                                    */
    
                            /*
                            //实际裁剪比例,可酌情自行调整
                            int left = 100;
                            int cliped_left = 0;
    
                            int top = 0;
                            int cliped_top = 0;
    
                            int cliped_width = width_;
                            int cliped_height = height_;
    
                            if(scale_rate == SCALE_RATE_HALF)
                            {
                                cliped_left = left / 2;
                                cliped_top = top / 2;
    
                                //宽度裁剪后,展示3/4比例
                                cliped_width = (width_ *3)/4;
                                //高度不做裁剪
                                cliped_height = height_;
                            }
                            else if(scale_rate == SCALE_RATE_TWO_FIFTHS)
                            {
                                cliped_left = left * 2 / 5;
                                cliped_top = top * 2 / 5;
    
                                //宽度裁剪后,展示3/4比例
                                cliped_width = (width_ *3)/4;
                                //高度不做裁剪
                                cliped_height = height_;
                            }
    
                            if(cliped_width % 2 != 0)
                            {
                                cliped_width = cliped_width + 1;
                            }
    
                            if(cliped_height % 2 != 0)
                            {
                                cliped_height = cliped_height + 1;
                            }
    
                            if ( (cliped_left + cliped_width) > width_)
                            {
                                Log.e(TAG, " invalid cliped region settings, cliped_left: " + cliped_left + " cliped_" + cliped_width + " " + width_);
    
                                return;
                            }
    
                            if ( (cliped_top + cliped_height) > height_)
                            {
                                Log.e(TAG, "invalid cliped region settings, cliped_top: " + cliped_top + " cliped_height:" + cliped_height + " height:" + height_);
    
                                return;
                            }
    
                            //Log.i(TAG, " clipLeft: " + cliped_left + " clipTop: " + cliped_top +  " clipWidth: " + cliped_width + " clipHeight: " + cliped_height);
    
                            libPublisher.SmartPublisherOnCaptureVideoClipedRGBAData(publisherHandle, last_buffer, row_stride_,
                                    width_, height_, cliped_left, cliped_top, cliped_width, cliped_height );
                            */
    
                           // Log.i(TAG, "post data: " + last_post_time + " cost:" + post_cost_time);
                        }
                        else
                        {
                            try {
                                Thread.sleep(10);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
    
                if ( last_image != null)
                {
                    last_image.close();
                    last_image = null;
                }
            }
        }
    

    关闭采集推送:

        public void onDestroy() {
            // TODO Auto-generated method stub
            Log.i(TAG, "Service stopped..");
    
            stopScreenCapture();
    
            clearAllImages();
    
            if( is_post_data_thread_alive && post_data_thread != null )
            {
                Log.i(TAG, "onDestroy close post_data_thread++");
    
                is_post_data_thread_alive = false;
    
                try {
                    post_data_thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                post_data_thread = null;
    
                Log.i(TAG, "onDestroy post_data_thread closed--");
            }
    
            if (isPushingRtmp || isRecording || isRTSPPublisherRunning)
            {
                if (audioRecord_ != null) {
                    Log.i(TAG, "surfaceDestroyed, call StopRecording..");
    
                    audioRecord_.Stop();
    
                    if (audioRecordCallback_ != null) {
                        audioRecord_.RemoveCallback(audioRecordCallback_);
                        audioRecordCallback_ = null;
                    }
    
                    audioRecord_ = null;
                }
    
                stopPush();
                isPushingRtmp = false;
    
                stopRecorder();
                isRecording = false;
    
                stopRtspPublisher();
                isRTSPPublisherRunning = false;
    
                stopRtspService();
                isRTSPServiceRunning = false;
    
                if (publisherHandle != 0) {
                    if (libPublisher != null) {
                        libPublisher.SmartPublisherClose(publisherHandle);
                        publisherHandle = 0;
                    }
                }
            }
    
            libPublisher.UnInitRtspServer();
    
            super.onDestroy();
        }

    以上就是Android平台数据采集、编码并推送的大概流程,感兴趣的开发者可参考下。

  • 相关阅读:
    九度OJ 1035:找出直系亲属 (二叉树、递归)
    九度OJ 1034:寻找大富翁 (排序)
    九度OJ 1033:继续xxx定律 (基础题)
    九度OJ 1032:ZOJ (基础题)
    centos 6.4 安装mongodb
    数据校验工具类
    《 mongodb 学习 》java 基本操作
    《 mongodb 学习 》基本操作2
    《 mongodb 学习 》之基本操作
    《 mongodb 学习 》之安装篇
  • 原文地址:https://www.cnblogs.com/daniulivesdk/p/13126852.html
Copyright © 2020-2023  润新知