Android平台如何实现屏幕数据采集并推送至RTMP服务器

简介: 随着无纸化、智慧教室等场景的普及,好多企业或者开发者开始寻求更高效稳定低延迟的RTMP同屏方案,本文以大牛直播SDK(Github)的同屏demo(对应工程:SmartServicePublisherV2)为例,介绍下如何采集编码推送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_width:" + cliped_width + " 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平台数据采集、编码并推送的大概流程,感兴趣的开发者可参考下。

相关文章
|
2月前
|
机器学习/深度学习 Android开发 数据安全/隐私保护
手机脚本录制器, 脚本录制器安卓,识图识色屏幕点击器【autojs】
完整的UI界面,包含录制控制按钮和状态显示 屏幕点击动作录制功能,记录点击坐标和时间间隔
|
3月前
|
监控 Android开发 数据安全/隐私保护
批量发送短信的平台,安卓群发短信工具插件脚本,批量群发短信软件【autojs版】
这个Auto.js脚本实现了完整的批量短信发送功能,包含联系人管理、短信内容编辑、发送状态监控等功能
|
7月前
|
存储 编解码 监控
Android平台GB28181执法记录仪技术方案与实现
本文介绍了大牛直播SDK的SmartGBD在执法记录仪场景中的应用。GB28181协议作为视频监控联网的国家标准,为设备互联互通提供规范。SmartGBD专为Android平台设计,支持音视频采集、编码与传输,具备自适应算法和多功能扩展优势。文章分析了执法记录仪的需求,如实时音视频传输、设备管理及数据安全,并详细阐述了基于SmartGBD的技术实现方案,包括环境准备、SDK集成、设备注册、音视频处理及功能扩展等步骤。最后展望了SmartGBD在未来智慧物联领域的广阔应用前景。
269 13
|
7月前
|
存储 编解码 开发工具
Android平台毫秒级低延迟HTTP-FLV直播播放器技术探究与实现
本文详细探讨了在Android平台上实现HTTP-FLV播放器的过程。首先介绍了FLV格式的基础,包括文件头和标签结构。接着分析了HTTP-FLV传输原理,通过分块传输实现流畅播放。然后重点讲解了播放器的实现步骤,涵盖网络请求、数据解析、音视频解码与渲染,以及播放控制功能的设计。文章还讨论了性能优化和网络异常处理的方法,并总结了HTTP-FLV播放器的技术价值,尤其是在特定场景下的应用意义。
263 11
|
7月前
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。
|
8天前
|
弹性计算 运维 安全
阿里云轻量应用服务器详解——2025升级到200M峰值带宽
阿里云轻量应用服务器(Simple Application Server)是面向个人开发者及中小企业的轻量级云服务,适用于网站搭建、开发测试、小程序后端等场景。2025年升级至200M峰值带宽,支持WordPress、宝塔面板、Docker等应用镜像一键部署,操作简单,运维便捷。按套餐售卖,不支持自定义CPU内存配置,价格低至38元/年起,是快速上云的高性价比选择。
|
1月前
|
存储 缓存 数据挖掘
阿里云目前最便宜云服务器介绍:38元、99元、199元性能,选购攻略参考
轻量应用服务器2核2G峰值200M带宽38元1年;云服务器经济型e实例2核2G3M带宽99元1年;云服务器通用算力型u1实例2核4G5M带宽199元1年。对于还未使用过阿里云服务器的用户来说,大家也不免有些疑虑,这些云服务器性能究竟如何?它们适用于哪些场景?能否满足自己的使用需求呢?接下来,本文将为您全方位介绍这几款云服务器,以供您了解及选择参考。
|
2月前
|
网络安全 云计算
如何设置阿里云轻量应用服务器镜像?
本文介绍了在阿里云轻量应用服务器上创建与配置镜像的详细步骤。镜像是一种特殊的文件系统映射,可用于快速克隆服务器配置。内容涵盖准备条件、登录控制台、创建实例、生成镜像、下载与设置镜像,以及如何使用镜像启动新实例。适合希望提升服务器部署效率的用户参考。
|
17天前
|
弹性计算 Devops Shell
用阿里云 DevOps Flow 实现 ECS 部署自动化:从准备到落地的完整指南
阿里云 DevOps Flow 是一款助力开发者实现自动化部署的高效工具,支持代码流水线构建、测试与部署至ECS实例,显著提升交付效率与稳定性。本文详解如何通过 Flow 自动部署 Bash 脚本至 ECS,涵盖环境准备、流水线搭建、源码接入、部署流程设计及结果验证,助你快速上手云上自动化运维。
75 0
|
6天前
|
开发框架 JavaScript .NET
阿里云轻量应用服务器2核2G38元1年起怎么样?性能、应用场景与购买价值参考
目前在阿里云的活动中,抢购价为38元1年的轻量应用服务器受到了众多个人和中小企业用户的高度关注,该款轻量应用服务器置为2核CPU、2G内存,峰值带宽达200M。那么,此款轻量应用服务器的具体性能如何?适用于哪些应用场景?是否具备较高的购买价值?本文将针对这款特惠轻量应用服务器展开全面且深入的测评与介绍。
162 30
阿里云轻量应用服务器2核2G38元1年起怎么样?性能、应用场景与购买价值参考

热门文章

最新文章