zoukankan      html  css  js  c++  java
  • 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平台数据采集、编码并推送的大概流程,感兴趣的开发者可参考下。

  • 相关阅读:
    Jessica's Reading Problem POJ
    FatMouse and Cheese HDU
    How many ways HDU
    Humble Numbers HDU
    Doing Homework again
    Stacks of Flapjacks UVA
    Party Games UVA
    24. 两两交换链表中的节点
    面试题 03.04. 化栈为队
    999. 可以被一步捕获的棋子数
  • 原文地址:https://www.cnblogs.com/daniulivesdk/p/13126852.html
Copyright © 2011-2022 走看看