Android视频直播的实现(推流完整实现001)

Android使用RtmpDump进行RTMP推流介绍 目前Android端APP视频直播一般使用RTMP协议推流到流媒体服务器,通过CDN加速后,然后在播放端通过HTTP-FLV协议或HLS协议拉流播放。 #RTMP协议介绍 RTMP协议(Real Time Message Protocol,实时信息传输协议),是由Adobe公司提出的一种应用层的协议,可用于实时传递音视频媒体数据。它基于传输层的TCP协议,通过与服务端建立长连接来传递数据。相较于其它同类协议,延迟较低,一般在1~3s,非常适合用于直播场景下的推流。 ... 阅读详情
AI摘要

本文介绍了一套免费工具构建的视频直播方案,详细分析了视频直播的流程,包括采集、处理、编码、推流到服务器、服务器流分发及播放器播放。并通过代码示例展示了如何实现视频采集和推流。

http://blog.youkuaiyun.com/huaxun66/article/details/53427771 


http://blog.youkuaiyun.com/huaxun66/article/details/53427771


最近一段时间,视频直播可谓大火。在视频直播领域,有不同的商家提供各种的商业解决方案,包括软硬件设备,摄像机,编码器,流媒体服务器等。本文要讲解的是如何使用一系列免费工具,打造一套视频直播方案。

视频直播流程

视频直播的流程可以分为如下几步: 
采集 —>处理—>编码和封装—>推流到服务器—>服务器流分发—>播放器流播放

这里写图片描述

1.采集

采集是整个视频推流过程中的第一个环节,它从系统的采集设备中获取原始视频数据,将其输出到下一个环节。视频的采集涉及两方面数据的采集:音频采集和图像采集,它们分别对应两种完全不同的输入源和数据格式。

这里写图片描述

  • 音频采集 
    音频数据既能与图像结合组合成视频数据,也能以纯音频的方式采集播放,后者在很多成熟的应用场景如在线电台和语音电台等起着非常重要的作用。音频的采集过程主要通过设备将环境中的模拟信号采集成 PCM 编码的原始数据,然后编码压缩成 MP3 等格式的数据分发出去。常见的音频压缩格式有:MP3,AAC,HE-AAC,Opus,FLAC,Vorbis (Ogg),Speex 和 AMR等。 
    音频采集和编码主要面临的挑战在于:延时敏感、卡顿敏感、噪声消除(Denoise)、回声消除(AEC)、静音检测(VAD)和各种混音算法等。

  • 图像采集 
    将图像采集的图片结果组合成一组连续播放的动画,即构成视频中可肉眼观看的内容。图像的采集过程主要由摄像头等设备拍摄成 YUV 编码的原始数据,然后经过编码压缩成 H.264 等格式的数据分发出去。常见的视频封装格式有:MP4、3GP、AVI、MKV、WMV、MPG、VOB、FLV、SWF、MOV、RMVB 和 WebM 等。 
    图像由于其直观感受最强并且体积也比较大,构成了一个视频内容的主要部分。图像采集和编码面临的主要挑战在于:设备兼容性差、延时敏感、卡顿敏感以及各种对图像的处理操作如美颜和水印等。

视频采集的采集源主要有 摄像头采集、屏幕录制和从视频文件推流。

2.处理

视频或者音频完成采集之后得到原始数据,为了增强一些现场效果或者加上一些额外的效果,我们一般会在将其编码压缩前进行处理,比如打上时间戳或者公司 Logo 的水印,祛斑美颜和声音混淆等处理。在主播和观众连麦场景中,主播需要和某个或者多个观众进行对话,并将对话结果实时分享给其他所有观众,连麦的处理也有部分工作在推流端完成。

这里写图片描述

如上图所示,处理环节中分为音频和视频处理,音频处理中具体包含混音、降噪和声音特效等处理,视频处理中包含美颜、水印、以及各种自定义滤镜等处理。

3.编码和封装

(1)编码

如果把整个流媒体比喻成一个物流系统,那么编解码就是其中配货和装货的过程,这个过程非常重要,它的速度和压缩比对物流系统的意义非常大,影响物流系统的整体速度和成本。同样,对流媒体传输来说,编码也非常重要,它的编码性能、编码速度和编码压缩比会直接影响整个流媒体传输的用户体验和传输成本。

  • 视频编码的意义 
    原始视频数据存储空间大,一个 1080P 的 7 s 视频需要 817 MB 
    原始视频数据传输占用带宽大,10 Mbps 的带宽传输上述 7 s 视频需要 11 分钟 
    而经过 H.264 编码压缩之后,视频大小只有 708 k ,10 Mbps 的带宽仅仅需要 500 ms ,可以满足实时传输的需求,所以从视频采集传感器采集来的原始视频势必要经过视频编码。

  • 基本原理 
    为什么巨大的原始视频可以编码成很小的视频呢?这其中的技术是什么呢?核心思想就是去除冗余信息: 
    1)空间冗余:图像相邻像素之间有较强的相关性 
    2)时间冗余:视频序列的相邻图像之间内容相似 
    3)编码冗余:不同像素值出现的概率不同 
    4)视觉冗余:人的视觉系统对某些细节不敏感 
    5)知识冗余:规律性的结构可由先验知识和背景知识得到

  • 编码器的选择 
    视频编码器经历了数十年的发展,已经从开始的只支持帧内编码演进到现如今的 H.265 和 VP9 为代表的新一代编码器,下面是一些常见的视频编码器: 
    1)H.264/AVC 
    2)HEVC/H.265 
    3)VP8 
    4)VP9 
    5)FFmpeg 
    注:音频编码器有Mp3, AAC等。

(2)封装 
沿用前面的比喻,封装可以理解为采用哪种货车去运输,也就是媒体的容器。 
所谓容器,就是把编码器生成的多媒体内容(视频,音频,字幕,章节信息等)混合封装在一起的标准。容器使得不同多媒体内容同步播放变得很简单,而容器的另一个作用就是为多媒体内容提供索引,也就是说如果没有容器存在的话一部影片你只能从一开始看到最后,不能拖动进度条,而且如果你不自己去手动另外载入音频就没有声音。下面是几种常见的封装格式: 
1)AVI 格式(后缀为 .avi) 
2)DV-AVI 格式(后缀为 .avi) 
3)QuickTime File Format 格式(后缀为 .mov) 
4)MPEG 格式(文件后缀可以是 .mpg .mpeg .mpe .dat .vob .asf .3gp .mp4等) 
5)WMV 格式(后缀为.wmv .asf) 
6)Real Video 格式(后缀为 .rm .rmvb) 
7)Flash Video 格式(后缀为 .flv) 
8)Matroska 格式(后缀为 .mkv) 
9)MPEG2-TS 格式 (后缀为 .ts) 
目前,我们在流媒体传输,尤其是直播中主要采用的就是 FLV 和 MPEG2-TS 格式,分别用于 RTMP/HTTP-FLV 和 HLS 协议。

4.推流到服务器

推流是直播的第一公里,直播的推流对这个直播链路影响非常大,如果推流的网络不稳定,无论我们如何做优化,观众的体验都会很糟糕。所以也是我们排查问题的第一步,如何系统地解决这类问题需要我们对相关理论有基础的认识。 
推送协议主要有三种:

  • RTSP(Real Time Streaming Protocol):实时流传送协议,是用来控制声音或影像的多媒体串流协议, 由Real Networks和Netscape共同提出的;
  • RTMP(Real Time Messaging Protocol):实时消息传送协议,是Adobe公司为Flash播放器和服务器之间音频、视频和数据传输 开发的开放协议;
  • HLS(HTTP Live Streaming):是苹果公司(Apple Inc.)实现的基于HTTP的流媒体传输协议;

RTMP协议基于 TCP,是一种设计用来进行实时数据通信的网络协议,主要用来在 flash/AIR 平台和支持 RTMP 协议的流媒体/交互服务器之间进行音视频和数据通信。支持该协议的软件包括 Adobe Media Server/Ultrant Media Server/red5 等。 
它有三种变种:

  • RTMP工作在TCP之上的明文协议,使用端口1935;
  • RTMPT封装在HTTP请求之中,可穿越防火墙;
  • RTMPS类似RTMPT,但使用的是HTTPS连接;

RTMP 是目前主流的流媒体传输协议,广泛用于直播领域,可以说市面上绝大多数的直播产品都采用了这个协议。 
RTMP协议就像一个用来装数据包的容器,这些数据可以是AMF格式的数据,也可以是FLV中的视/音频数据。一个单一的连接可以通过不同的通道传输多路网络流。这些通道中的包都是按照固定大小的包传输的。 
这里写图片描述

5.服务器流分发

流媒体服务器的作用是负责直播流的发布和转播分发功能。 
流媒体服务器有诸多选择,如商业版的Wowza。但我选择的是Nginx,它是一款优秀的免费Web服务器,后面我会详细介绍如何搭建Nginx服务器。

6.播放器流播放

主要是实现直播节目在终端上的展现。因为我这里使用的传输协议是RTMP, 所以只要支持 RTMP 流协议的播放器都可以使用,譬如:

  • 电脑端:VLC等
  • 手机端:Vitamio以及ijkplayer等

一般情况下我们把上面流程的前四步称为第一部分,即视频主播端的操作。视频采集处理后推流到流媒体服务器,第一部分功能完成。第二部分就是流媒体服务器,负责把从第一部分接收到的流进行处理并分发给观众。第三部分就是观众啦,只需要拥有支持流传输协议的播放器即可。 
这里写图片描述

第一部分:采集推流SDK

目前市面上集视频采集、编码、封装和推流于一体的SDK已经有很多了,例如商业版的NodeMedia,但NodeMedia SDK按包名授权,未授权包名应用使用有版权提示信息。 
我这里使用的是别人分享在github上的一个免费SDK,下载地址。

下面我就代码分析一下直播推流的过程吧: 
先看入口界面: 
这里写图片描述 
很简单,一个输入框让你填写服务器的推流地址,另外一个按钮开启推流。

public class StartActivity extends Activity {
    public static final String RTMPURL_MESSAGE = "rtmppush.hx.com.rtmppush.rtmpurl";
    private Button _startRtmpPushButton = null;
    private EditText _rtmpUrlEditText = null;

    private View.OnClickListener _startRtmpPushOnClickedEvent = new View.OnClickListener() {
        @Override
        public void onClick(View arg0) {
            Intent i = new Intent(StartActivity.this, MainActivity.class);
            String rtmpUrl = _rtmpUrlEditText.getText().toString();
            i.putExtra(StartActivity.RTMPURL_MESSAGE, rtmpUrl);
            StartActivity.this.startActivity(i);
        }
    };

    private void InitUI(){
        _rtmpUrlEditText = (EditText)findViewById(R.id.rtmpUrleditText);
        _startRtmpPushButton = (Button)findViewById(R.id.startRtmpButton);
        _rtmpUrlEditText.setText("rtmp://192.168.1.104:1935/live/12345");
        _startRtmpPushButton.setOnClickListener(_startRtmpPushOnClickedEvent);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_start);
        InitUI();
    }
}
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

主要的推流过程在MainActivity里面,同样,先看界面: 
这里写图片描述 
布局文件:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/cameraRelative"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:theme="@android:style/Theme.NoTitleBar.Fullscreen">

<SurfaceView
        android:id="@+id/surfaceViewEx"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    <Button
        android:id="@+id/SwitchCamerabutton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBottom="@+id/surfaceViewEx"
        android:text="@string/SwitchCamera" />
</RelativeLayout>
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

其实就是用一个SurfaceView显示摄像头拍摄画面,并提供了一个按钮切换前置和后置摄像头。从入口函数看起:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);
        this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

        Intent intent = getIntent();
        _rtmpUrl = intent.getStringExtra(StartActivity.RTMPURL_MESSAGE);

        InitAll();

        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
        _wakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "My Tag");
    }
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

首先设置全屏显示,常亮,竖屏,获取服务器的推流url,再初始化所有东西。

    private void InitAll() {
        WindowManager wm = this.getWindowManager();

        int width = wm.getDefaultDisplay().getWidth();
        int height = wm.getDefaultDisplay().getHeight();
        int iNewWidth = (int) (height * 3.0 / 4.0);

        RelativeLayout rCameraLayout = (RelativeLayout) findViewById(R.id.cameraRelative);
        RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT,
                RelativeLayout.LayoutParams.MATCH_PARENT);
        int iPos = width - iNewWidth;
        layoutParams.setMargins(iPos, 0, 0, 0);

        _mSurfaceView = (SurfaceView) this.findViewById(R.id.surfaceViewEx);
        _mSurfaceView.getHolder().setFixedSize(HEIGHT_DEF, WIDTH_DEF);
        _mSurfaceView.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        _mSurfaceView.getHolder().setKeepScreenOn(true);
        _mSurfaceView.getHolder().addCallback(new SurceCallBack());
        _mSurfaceView.setLayoutParams(layoutParams);

        InitAudioRecord();

        _SwitchCameraBtn = (Button) findViewById(R.id.SwitchCamerabutton);
        _SwitchCameraBtn.setOnClickListener(_switchCameraOnClickedEvent);

        RtmpStartMessage();//开始推流
    }
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

首先设置屏幕比例3:4显示,给SurfaceView设置一些参数并添加回调,再初始化AudioRecord,最后执行开始推流。音频在这里初始化了,那么相机在哪里初始化呢?其实在SurfaceView的回调函数里。

        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            _iDegrees = getDisplayOritation(getDispalyRotation(), 0);
            if (_mCamera != null) {
                InitCamera(); //初始化相机
                return;
            }
            //华为i7前后共用摄像头
            if (Camera.getNumberOfCameras() == 1) {
                _bIsFront = false;
                _mCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_BACK);
            } else {
                _mCamera = Camera.open(Camera.CameraInfo.CAMERA_FACING_FRONT);
            }
            InitCamera();
        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
        }
    }
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

相机的初始化就在这里啦:

    public void InitCamera() {
        Camera.Parameters p = _mCamera.getParameters();

        Size prevewSize = p.getPreviewSize();
        showlog("Original Width:" + prevewSize.width + ", height:" + prevewSize.height);

        List<Size> PreviewSizeList = p.getSupportedPreviewSizes();
        List<Integer> PreviewFormats = p.getSupportedPreviewFormats();
        showlog("Listing all supported preview sizes");
        for (Camera.Size size : PreviewSizeList) {
            showlog("  w: " + size.width + ", h: " + size.height);
        }

        showlog("Listing all supported preview formats");
        Integer iNV21Flag = 0;
        Integer iYV12Flag = 0;
        for (Integer yuvFormat : PreviewFormats) {
            showlog("preview formats:" + yuvFormat);
            if (yuvFormat == android.graphics.ImageFormat.YV12) {
                iYV12Flag = android.graphics.ImageFormat.YV12;
            }
            if (yuvFormat == android.graphics.ImageFormat.NV21) {
                iNV21Flag = android.graphics.ImageFormat.NV21;
            }
        }

        if (iNV21Flag != 0) {
            _iCameraCodecType = iNV21Flag;
        } else if (iYV12Flag != 0) {
            _iCameraCodecType = iYV12Flag;
        }
        p.setPreviewSize(HEIGHT_DEF, WIDTH_DEF);
        p.setPreviewFormat(_iCameraCodecType);
        p.setPreviewFrameRate(FRAMERATE_DEF);

        showlog("_iDegrees="+_iDegrees);
        _mCamera.setDisplayOrientation(_iDegrees);
        p.setRotation(_iDegrees);
        _mCamera.setPreviewCallback(_previewCallback);
        _mCamera.setParameters(p);
        try {
            _mCamera.setPreviewDisplay(_mSurfaceView.getHolder());
        } catch (Exception e) {
            return;
        }
        _mCamera.cancelAutoFocus();//只有加上了这一句,才会自动对焦。
        _mCamera.startPreview();
    }
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

还记得之前初始化完成之后开始推流函数吗?

    private void RtmpStartMessage() {
        Message msg = new Message();
        msg.what = ID_RTMP_PUSH_START;
        Bundle b = new Bundle();
        b.putInt("ret", 0);
        msg.setData(b);
        mHandler.sendMessage(msg);
    }
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Handler处理:

    public Handler mHandler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            Bundle b = msg.getData();
            int ret;
            switch (msg.what) {
                case ID_RTMP_PUSH_START: {
                    Start();
                    break;
                }
            }
        }
    };
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

真正的推流实现原来在这里:

    private void Start() {
        if (DEBUG_ENABLE) {
            File saveDir = Environment.getExternalStorageDirectory();
            String strFilename = saveDir + "/aaa.h264";
            try {
                if (!new File(strFilename).exists()) {
                    new File(strFilename).createNewFile();
                }
                _outputStream = new DataOutputStream(new FileOutputStream(strFilename));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        //_rtmpSessionMgr.Start("rtmp://192.168.0.110/live/12345678");
        _rtmpSessionMgr = new RtmpSessionManager();
        _rtmpSessionMgr.Start(_rtmpUrl); //------point 1

        int iFormat = _iCameraCodecType;
        _swEncH264 = new SWVideoEncoder(WIDTH_DEF, HEIGHT_DEF, FRAMERATE_DEF, BITRATE_DEF);
        _swEncH264.start(iFormat); //------point 2

        _bStartFlag = true;

        _h264EncoderThread = new Thread(_h264Runnable);
        _h264EncoderThread.setPriority(Thread.MAX_PRIORITY);
        _h264EncoderThread.start(); //------point 3

        _AudioRecorder.startRecording();
        _AacEncoderThread = new Thread(_aacEncoderRunnable);
        _AacEncoderThread.setPriority(Thread.MAX_PRIORITY);
        _AacEncoderThread.start(); //------point 4
    }
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

里面主要的函数有四个,我分别标出来了,现在我们逐一看一下。首先是point 1,这已经走到SDK里面了

    public int Start(String rtmpUrl){
        int iRet = 0;

        _rtmpUrl = rtmpUrl;
        _rtmpSession = new RtmpSession();

        _bStartFlag = true;
        _h264EncoderThread.setPriority(Thread.MAX_PRIORITY);
        _h264EncoderThread.start();

        return iRet;
    }
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

其实就是启动了一个线程,这个线程稍微有点复杂

    private Thread _h264EncoderThread = new Thread(new Runnable() {

        private Boolean WaitforReConnect(){
            for(int i=0; i < 500; i++){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(_h264EncoderThread.interrupted() || (!_bStartFlag)){
                    return false;
                }
            }
            return true;
        }
        @Override
        public void run() {
            while (!_h264EncoderThread.interrupted() && (_bStartFlag)) {
                if(_rtmpHandle == 0) {
                    _rtmpHandle = _rtmpSession.RtmpConnect(_rtmpUrl);
                    if(_rtmpHandle == 0){
                        if(!WaitforReConnect()){
                            break;
                        }
                        continue;
                    }
                }else{
                    if(_rtmpSession.RtmpIsConnect(_rtmpHandle) == 0){
                        _rtmpHandle = _rtmpSession.RtmpConnect(_rtmpUrl);
                        if(_rtmpHandle == 0){
                            if(!WaitforReConnect()){
                                break;
                            }
                            continue;
                        }
                    }
                }

                if((_videoDataQueue.size() == 0) && (_audioDataQueue.size()==0)){
                    try {
                        Thread.sleep(30);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    continue;
                }
                //Log.i(TAG, "VideoQueue length="+_videoDataQueue.size()+", AudioQueue length="+_audioDataQueue.size());
                for(int i = 0; i < 100; i++){
                    byte[] audioData = GetAndReleaseAudioQueue();
                    if(audioData == null){
                        break;
                    }
                    //Log.i(TAG, "###RtmpSendAudioData:"+audioData.length);
                    _rtmpSession.RtmpSendAudioData(_rtmpHandle, audioData, audioData.length);
                }

                byte[] videoData = GetAndReleaseVideoQueue();
                if(videoData != null){
                    //Log.i(TAG, "$$$RtmpSendVideoData:"+videoData.length);
                    _rtmpSession.RtmpSendVideoData(_rtmpHandle, videoData, videoData.length);
                }
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            _videoDataQueueLock.lock();
            _videoDataQueue.clear();
            _videoDataQueueLock.unlock();
            _audioDataQueueLock.lock();
            _audioDataQueue.clear();
            _audioDataQueueLock.unlock();

            if((_rtmpHandle != 0) && (_rtmpSession != null)){
                _rtmpSession.RtmpDisconnect(_rtmpHandle);
            }
            _rtmpHandle  = 0;
            _rtmpSession = null;
        }
    });
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81

看18行,主要就是一个while循环,每隔一段时间去_audioDataQueue和_videoDataQueue两个缓冲数组中取数据发送给服务器,发送方法_rtmpSession.RtmpSendAudioData和_rtmpSession.RtmpSendVideoData都是Native方法,通过jni调用so库文件的内容,每隔一段时间,这个时间是多少呢?看第4行,原来是5秒钟,也就是说我们的视频数据会在缓冲中存放5秒才被取出来发给服务器,所有直播会有5秒的延时,我们可以修改这块来控制直播延时。 
上面说了我们会从_audioDataQueue和_videoDataQueue两个Buffer里面取数据,那么数据是何时放进去的呢?看上面的point 2,3,4。首先是point 2,同样走进了SDK:

    public boolean start(int iFormateType){
        int iType = OpenH264Encoder.YUV420_TYPE;

        if(iFormateType == android.graphics.ImageFormat.YV12){
            iType = OpenH264Encoder.YUV12_TYPE;
        }else{
            iType = OpenH264Encoder.YUV420_TYPE;
        }
        _OpenH264Encoder = new OpenH264Encoder();
        _iHandle = _OpenH264Encoder.InitEncode(_iWidth, _iHeight, _iBitRate, _iFrameRate, iType);
        if(_iHandle == 0){
            return false;
        }

        _iFormatType = iFormateType;
        return true;
    }
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

其实这是初始化编码器,具体的初始化过程也在so文件,jni调用。point 3,4其实就是开启两个线程,那我们看看线程中具体实现吧。

    private Thread _h264EncoderThread = null;
    private Runnable _h264Runnable = new Runnable() {
        @Override
        public void run() {
            while (!_h264EncoderThread.interrupted() && _bStartFlag) {
                int iSize = _YUVQueue.size();
                if (iSize > 0) {
                    _yuvQueueLock.lock();
                    byte[] yuvData = _YUVQueue.poll();
                    if (iSize > 9) {
                        Log.i(LOG_TAG, "###YUV Queue len=" + _YUVQueue.size() + ", YUV length=" + yuvData.length);
                    }

                    _yuvQueueLock.unlock();
                    if (yuvData == null) {
                        continue;
                    }

                    if (_bIsFront) {
                        _yuvEdit = _swEncH264.YUV420pRotate270(yuvData, HEIGHT_DEF, WIDTH_DEF);
                    } else {
                        _yuvEdit = _swEncH264.YUV420pRotate90(yuvData, HEIGHT_DEF, WIDTH_DEF);
                    }
                    byte[] h264Data = _swEncH264.EncoderH264(_yuvEdit);
                    if (h264Data != null) {
                        _rtmpSessionMgr.InsertVideoData(h264Data);
                        if (DEBUG_ENABLE) {
                            try {
                                _outputStream.write(h264Data);
                                int iH264Len = h264Data.length;
                                //Log.i(LOG_TAG, "Encode H264 len="+iH264Len);
                            } catch (IOException e1) {
                                e1.printStackTrace();
                            }
                        }
                    }
                }
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            _YUVQueue.clear();
        }
    };
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

也是一个循环线程,第9行,从_YUVQueue中取出摄像头获取的数据,然后进行视频旋转,第24行,对数据进行编码,然后执行26行,InsertVideoData:

    public void InsertVideoData(byte[] videoData){
        if(!_bStartFlag){
            return;
        }
        _videoDataQueueLock.lock();
        if(_videoDataQueue.size() > 50){
            _videoDataQueue.clear();
        }
        _videoDataQueue.offer(videoData);
        _videoDataQueueLock.unlock();
    }
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

果然就是插入之前提到的_videoDataQueue的Buffer。这里插入的是视频数据,那么音频数据呢?在另外一个线程,内容大致相同

private Runnable _aacEncoderRunnable = new Runnable() {
        @Override
        public void run() {
            DataOutputStream outputStream = null;
            if (DEBUG_ENABLE) {
                File saveDir = Environment.getExternalStorageDirectory();
                String strFilename = saveDir + "/aaa.aac";
                try {
                    if (!new File(strFilename).exists()) {
                        new File(strFilename).createNewFile();
                    }
                    outputStream = new DataOutputStream(new FileOutputStream(strFilename));
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }

            long lSleepTime = SAMPLE_RATE_DEF * 16 * 2 / _RecorderBuffer.length;

            while (!_AacEncoderThread.interrupted() && _bStartFlag) {
                int iPCMLen = _AudioRecorder.read(_RecorderBuffer, 0, _RecorderBuffer.length); // Fill buffer
                if ((iPCMLen != _AudioRecorder.ERROR_BAD_VALUE) && (iPCMLen != 0)) {
                    if (_fdkaacHandle != 0) {
                        byte[] aacBuffer = _fdkaacEnc.FdkAacEncode(_fdkaacHandle, _RecorderBuffer);
                        if (aacBuffer != null) {
                            long lLen = aacBuffer.length;

                            _rtmpSessionMgr.InsertAudioData(aacBuffer);
                            //Log.i(LOG_TAG, "fdk aac length="+lLen+" from pcm="+iPCMLen);
                            if (DEBUG_ENABLE) {
                                try {
                                    outputStream.write(aacBuffer);
                                } catch (IOException e) {
                                    // TODO Auto-generated catch block
                                    e.printStackTrace();
                                }
                            }
                        }
                    }
                } else {
                    Log.i(LOG_TAG, "######fail to get PCM data");
                }
                try {
                    Thread.sleep(lSleepTime / 10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Log.i(LOG_TAG, "AAC Encoder Thread ended ......");
        }
    };
    private Thread _AacEncoderThread = null;
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

这就是通过循环将音频数据插入_audioDataQueue这个Buffer。 
以上就是视频采集和推流的代码分析,Demo中并没有对视频进行任何处理,只是摄像头采集,编码后推流到服务器端。

第二部分:Nginx服务器搭建

流媒体服务器有诸多选择,如商业版的Wowza。但我选择的是免费的Nginx(nginx-rtmp-module)。Nginx本身是一个非常出色的HTTP服务器,它通过nginx的模块nginx-rtmp-module可以搭建一个功能相对比较完善的流媒体服务器。这个流媒体服务器可以支持RTMP和HLS。 
Nginx配合SDK做流媒体服务器的原理是: Nginx通过rtmp模块提供rtmp服务, SDK推送一个rtmp流到Nginx, 然后客户端通过访问Nginx来收看实时视频流。 HLS也是差不多的原理,只是最终客户端是通过HTTP协议来访问的,但是SDK推送流仍然是rtmp的。 
下面是一款已经集成rtmp模块的windows版本的Nginx。下载后,即可直接使用 
下载链接:https://github.com/illuspas/nginx-rtmp-win32

1、rtmp端口配置 
配置文件在/conf/nginx.conf 
RTMP监听 1935 端口,启用live 和hls 两个application 
这里写图片描述 
所以你的流媒体服务器url可以写成:rtmp://(服务器IP地址):1935/live/xxx 或 rtmp://(服务器IP地址):1935/hls/xxx 
例如我们上面写的 rtmp://192.168.1.104:1935/live/12345

HTTP监听 8080 端口,

  • :8080/stat 查看stream状态
  • :8080/index.html 为一个直播播放与直播发布测试器
  • :8080/vod.html 为一个支持RTMP和HLS点播的测试器

2、启动nginx服务 
双击nginx.exe文件或者在dos窗口下运行nginx.exe,即可启动nginx服务: 
这里写图片描述

1)启动任务管理器,可以看到nginx.exe进程 
这里写图片描述

2)打开网页输入http://localhot:8080,出现如下画面: 
这里写图片描述 
显示以上界面说明启动成功。

第三部分:直播流的播放

主播界面: 
这里写图片描述

上面说过了只要支持RTMP流传输协议的播放器都可以收看到我们的直播。下面举两个例子吧: 
(1)window端播放器VLC 
这里写图片描述

这里写图片描述

(2)Android端播放器ijkplayer 
ijkplayer的使用请参考Android ijkplayer的使用解析

    private void initPlayer() {
        player = new PlayerManager(this);
        player.setFullScreenOnly(true);
        player.setScaleType(PlayerManager.SCALETYPE_FILLPARENT);
        player.playInFullScreen(true);
        player.setPlayerStateListener(this);
        player.play("rtmp://192.168.1.104:1935/live/12345");
    }
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这里写图片描述

Rtmp Push Demo下载地址

顶
7
 
踩
0
 

Android端实现基于RTMP的音视频推流 出现这个错误是因为使用最新版的NDK造成的,最新版的NDk将头文件和库文件进行了分离,我们指定的sysroot文件夹下只有库文件,而头文件放在了NDK目录下的sysroot内,只需在--extra-cflags中添加 "-isysroot $NDK/sysroot" 即可,还有有关汇编的头文件也进行了分离,需要根据目标平台进行指定 "-I$NDK/sysroot/usr/include/arm-linux-androideabi",将 "arm-linux-androideabi" 改为需要的平台。 阅读详情

相关推荐

JavaCV音视频开发宝典:JavaCV实现mp3音频直播FM在线电台服务,无需流媒体服务,浏览器原生audio标签直接播放mp3,支持rtsp/rtmp/flv/hls/本地音视频源直接转码到mp3

《JavaCV音视频开发宝典》专栏目录导航 《JavaCV音视频开发宝典》专栏介绍和目录 ​ 前言 之前写过使用JavaCV实现webm直播的文章: 《JavaCV音视频开发宝典:使用JavaCV实现webm直播服务,无需流媒体服务,无需mse转封装插件(flv.js,hls.js),浏览器原生video标签直接播放rtsp,rtmp和桌面投屏直播》 ,群里有大佬突然问能不能实现mp3直播,我寻思了一下,应该可以,其实只要稍微修改一下上面链接里的webm直播这篇文章的代码就可以实现了,注意的点就是mp3.

eguid音视频方向博客,分享经过实践检验的硬核音视频技术。FFmpeg、openCV和JavaCV音视频和图像处理系列教程发布十周年,众多开发者的严格验证。欢迎关注,一起交流探索学习最新音视频和图像处理技术 2641

Android视频直播的实现

最近一段时间,视频直播可谓大火。在视频直播领域,有不同的商家提供各种的商业解决方案,包括软硬件设备,摄像机,编码器,流媒体服务器等。本文要讲解的是如何使用一系列免费工具,打造一套视频直播方案。 视频直播流程视频直播的流程可以分为如下几步: 采集 —>处理—>编码和封装—>推流到服务器—>服务器流分发—>播放器流播放1.采集采集是整个视频推流过程中的第一个环节,它从系统的采集设备中获取原始视频数据,

Watson的博客 6万+

开源视频直播推流SDK——探索携程移动直播的WebRTC技术

在移动直播领域,视频直播推流是一项关键技术,它允许用户通过移动设备将实时视频内容传输到互联网上。使用开源视频直播推流SDK,携程公司能够快速实现移动直播功能,并通过WebRTC技术提供高质量的实时视频传输。总结起来,开源视频直播推流SDK为携程移动直播的WebRTC探索提供了强大的工具和技术支持。通过集成该SDK,携程能够实现高品质的移动直播服务,为用户提供更加丰富和精彩的直播体验。在携程移动直播的探索中,我们选择了开源的WebRTC推流SDK作为实现的基础。最后,我们可以通过调用。

UnhcTransforms的博客 388

FFplay获取rtmb电视台直播数据流并播放

最近看电视台直播,网上直接看多广告,而且还要拉浏览器的性能,所以决定抓取直播电视台的rmtb地址使用FFplay来观看直播。 1.获取rmtb数据流 打开Chrome浏览器,选择一个直播节目,可以看到有十分多的广告,我们只要把这个视频在本地运行就可以屏蔽这些广告了。 接下来,我们打开Chrome开发者模式 然后选择网页的·html源码,然后寻找一个以rmtb协议开头的数据 2.使用FFplay播放 ffplay官方网址 下载ffplay后在可执行程序中打开命令行 然后输入命令ffplay -i rt.

ZesenYuan的博客 4405

从零开始:如何用大语言模型构建你的第一个路径规划智能体

本文详细介绍了如何利用大语言模型(LLM)构建路径规划智能体,从基础概念到实战案例,涵盖TSP和CVRP问题的解决方案。通过选择合适的LLM模型、设计有效的提示工程和实现自我验证,即使没有运筹学背景也能快速上手。文章还提供了部署与优化智能体的实用技巧,帮助读者在物流配送、机器人导航等领域应用这一技术。

weixin_28256233的博客 488

Docker入门教程:在腾讯云轻量服务器上部署你的第一个容器化应用 (2025)

还在为“在我电脑上明明是好的”而烦恼?本篇Docker入门教程,用通俗易懂的比喻解释镜像、容器的核心概念,并手把手带你在腾讯云轻量服务器上,从运行Nginx到编写Dockerfile打包自己的Python应用,轻松掌握容器化部署。

Clownseven的博客 2085

Android开发基于rtmp实现视频直播

前言 近两年时间,视频直播可谓大火。在视频直播领域,有不同的商家提供各种的商业解决方案,包括软硬件设备,摄像机,编码器,流媒体服务器等。本文要讲解的是如何使用一系列免费工具,打造一套视频直播方案。视频直播流程 视频直播的流程可以分为如下几步: 采集 —>处理—>编码和封装—>推流到服务器—>服务器流分发—>播...

weixin_34306593的博客 8173

MATLAB实战:用ARMAX模型搞定电力系统惯量评估(附完整代码)

本文详细介绍了如何利用MATLAB中的ARMAX模型进行电力系统惯量评估,解决新能源占比提高带来的系统惯量下降问题。通过系统辨识技术,从电网运行数据中准确提取惯量参数,并提供完整的代码实现,帮助工程师快速应用于实际工程。

peach的博客 637

Android视频直播推流的实现

最近一段时间,视频直播可谓大火。在视频直播领域,有不同的商家提供各种的商业解决方案,包括软硬件设备,摄像机,编码器,流媒体服务器等。本文要讲解的是如何使用一系列免费工具,打造一套视频直播方案。 视频直播流程 视频直播的流程可以分为如下几步: 采集 —>处理—>编码和封装—>推流到服务器—>服务器流分发—>播放器流播放 1.采集 采集是整个视频推流过程中的第一个环节,它从系统的采集设备中获取原始视频数据,将其输出到下一个环节。视频的采集涉及两方面数据的采集:音频采集和图像采集,

weixin_39432879的博客 1482

微观交通流仿真软件:SUMO (Simulation of Urban MObility)_(8).仿真场景创建与编辑

总结文件()包含了仿真过程中的总体统计信息,如车辆生成数、平均速度等。加载总结文件使用Python的提取关键信息通过以上步骤,可以使用SUMO及其相关工具和库创建和编辑交通仿真场景。无论是手动创建、使用工具转换现有数据,还是通过Python脚本自动生成,SUMO都提供了丰富的功能来满足不同需求。此外,Traci库允许在仿真过程中动态调整交通场景,从而实现更精细的控制和优化。最后,通过对输出文件的分析,可以评估仿真结果并进行进一步的改进。

kkchenjj的博客 1270

VAY 是 SAP 自动科目确定(OBYC-GBB)中的科目修改码,专用于带科目分配对象的销售订单发货场景,核心是触发 “Dr 成本要素科目(如销售费用 / 特定成本对象) Cr 库存商品” 的分录,

摘要:VAY是SAP中用于带科目分配对象(如成本中心、WBS)的销售订单发货的科目修改码,与标准VAX场景不同,它生成"借成本要素科目/贷库存商品"的分录。配置路径为OBYC-GBB-VAY,需确保借方科目为成本要素。典型业务场景包括免费样品、项目专用发货等。常见问题包括VAY未触发或科目记账错误,需检查销售订单科目分配、移动类型设置及OBYC配置。特殊场景建议单独配置科目以便费用分析。

金牌架构师 1254

视频直播推流不成功如何排查

本博文将介绍,视频直播加速配置后,推流不成功要如何进行排查及解决; 1. 排查对应的域名是否进行直播加速配置 要进行直播推流,用户必须得开启直播加速—》配置直播域名—》直播推流,可以参考【快速开始】; 2. 排查获取到的推流URL地址是否准确 2.1 如何获取推流地址 直播加速配置成功后,用户可以在阿里云控制台—》视频直播—》域名管理—》选择具体域名—》...

weixin_34174132的博客 4953

面试问题总结——关于YOLO系列(三)

接着上篇的的YOLO系列面试问题总结,加油,冲! 三.YOLOv3 1.YOLOv3主干网络BackBone——Darknet-53网络结构(因为有53个卷积层) (原论文中Darknet53的尺寸是在图片分类训练集上训练的,所以输入的图像尺寸是256x256,而在YOLOv3中输入尺寸是416×416)   ①上图中,Top-1 Accuracy和Top-5 Accuracy是指什么呢?区别在哪呢?   我们知道ImageNet有大概1000个分类,而模型预测某张图片时,会给出1000个按概率从高.

boss-dog 1万+

安卓实现视频直播(rtmp)以及直播推流(NDK实现)

前言 想写关于NDK的文章很久了,但一直不知道该写点啥,该怎么写?正好公司有一个直播和播放的需求,那好吧,就造一个视频播放和视频直播的轮子吧。那说干就干!声明一下,本篇文章不会写怎样配置NDK等问题,只是大概介绍一下思路,具体代码已经放在Github上,大家可以下载代码自己进行定制或者直接引入进行使用。如果有幸帮到各位,那就请点一个Star,不胜感激。下面是具体使用方法: 引入依赖 首先在项目的b...

Zhujiang 4118

矿石生成(1)

这个提供了标准化的矿石放置配置模板简化了常见和稀有矿石的配置过程遵循了Minecraft原版的世界生成模式提高了代码的可读性和可维护性通过使用这个类,模组开发者可以更轻松地配置矿石的生成规则,而不需要每次都手动编写完整的放置修饰符列表。这种设计模式在原版Minecraft和其他大型模组中也很常见,是一种良好的编程实践。// 导入语句...// 字段和方法...包声明表明这个类位于luckymod模组的WorldGen包中,专门处理世界生成相关配置。导入语句。

qq_24992377的博客 137

【教程4>第6章>第15节】 基于I+jQ复数信号的LS信道估计和均衡实现1——matlab仿真分析,QPSK为例

在基于QPSK调制的通信系统中,LS信道估计和均衡是用于补偿信道失真、提高信号传输质量的重要技术。以下将详细介绍基于I+jQ复数信号的 LS 信道估计和均衡的实现原理与过程。可以看到,通过线性插值之后,QPSK信号的星座图可以模糊的看到四个星座点(图2),通过非线性插值之后,可以清晰的看到QPSK的四个星座点(图3)。可以看到,信道均衡显著提高了通信系统的可靠性和有效性。

FPGA/MATLAB学习教程/源码/项目合作开发 657

android大疆无人机直播推流VLC

官方给了直播的方法:startStream,最近公司需求是大疆无人机做直播推流给后台服务器,然后在后台可以看到无人机拍摄的内容,实时监控无人机的状态 因为这个最新的大疆官方给出了方法,所以做起来是非常简单的,我是根据官方的demo修改来的,有疑问的请评论和私信,代码在我的csdn下载,可以找到资源 项目地址:https://github.com/wrs13634194612/cf8833.gi...

cf8833的博客 1万+

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

android 什么是推流和拉流

Android 推流与拉流详解 在移动应用开发中,视频直播成为了一项重要的功能,其中推流和拉流是其核心部分。推流是客户端将音频、视频数据上传至服务器的过程,而拉流则是客户端下载并播放服务器存储的音频、视频数据。本文将为你详细阐述推流和拉流的流程,以及如何在 Android 中实现这两项功能。 流程汇总 以下是推流和拉流的过...

weixin_31995343的博客 685

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

Android视频直播的实现(包括服务器搭建)

最近一段时间,视频直播可谓大火。在视频直播领域,有不同的商家提供各种的商业解决方案,包括软硬件设备,摄像机,编码器,流媒体服务器等。本文要讲解的是如何使用一系列免费工具,打造一套视频直播方案。 视频直播流程 视频直播的流程可以分为如下几步: 采集 —>处理—>编码和封装—>推流到服务器—>服务器流分发—>播放器流播放 1.采集 采集是整个视频推流过...

weixin_37897683的博客 4626

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

Android开发基于RTMP实现视频直播

前言 近两年时间,视频直播可谓大火。在视频直播领域,有不同的商家提供各种的商业解决方案,包括软硬件设备,摄像机,编码器,流媒体服务器等。本文要讲解的是如何使用一系列免费工具,打造一套视频直播方案。 视频直播流程 视频直播的流程可以分为如下几步: 采集 —&gt;处理—&gt;编码和封装—&gt;推流到服务器—&gt;服务器流分发—&gt;播放器流播放   一般情况下我们把流程...

haoxuhong的博客 8350

Android开发-在Android里接入腾讯云推流SDK实现直播推流的功能

前 言 如今,在国内移动互联网发展了几年的时间,移动开发技术也相对的成熟,在咱们日常使用的手机App中也少不了直播的功能,不管是娱乐类、游戏类、体育类还是教育类等的App都会有直播的功能,可以说直播的功能在一些商业应用或者非商业应用中都是不可或缺的功能。目前国内比较火直播App有如:斗鱼(游戏直播)、YY直播(全民娱乐直播)、虎牙(游戏+电竞直播)以及映客(娱乐直播)等直播。 而要想在自己的A...

_彼岸雨敲窗_的博客 6626

【Android RTMP】安卓直播推流总结 ( 直播服务器搭建 | NV21 图像采集 | H.264 视频编码 | PCM 音频采集 | AAC 音频编码 | RTMP 包封装推流 )

一、 安卓直播推流专栏博客总结、 二、 相关资源介绍、 三、 GitHub 源码地址、 四、 整体 Android 直播推流数据到服务器并观看直播演示过程

让 学习 成为一种 习惯 ( 韩曙亮 の 技术博客 ) 3027

android直播功能实现,Android视频直播功能实现流程

视频直播场景的主要结构及流程如下图所示:请注意:除了上述基本功能,开发者如果想使用 ZegoLiveRoom SDK 实现更高级的功能,例如混音、音频录制、外部滤镜等,请直接参考 互动视频-高级功能指南,本文不再赘述。为了便于开发者更快理解 LiveDemo5 中的逻辑,下述每节会将功能核心源码片段挑出来并加以讲解。开发者亦可直接阅读 LiveDemo5 源码,两者是一致的。1 单主播直播单主播直...

weixin_42099814的博客 2982

EasyRTMP:RTMP直播推流安卓Android EasyRTMP介绍

EasyRTMP是由青犀开放平台开发的一套RTMP直播推送功能组件,内部集成了包括:基本RTMP协议、断线重连、异步推送、环形缓冲区、推送网络拥塞自动丢帧、缓冲区关键帧检索、事件回调(断线、音视频数据回调),通过EasyRTMP我们就可以避免接触到稍显复杂的RTMP推送或者客户端流程,只需要调用EasyRTMP的几个API接口,就能轻松、稳定地进行流媒体音视频数据的推送,支持市面上绝大部分的RTMP流媒体服务器,包括Wowza、Red5、nginx-rtmp、crtmpserver等主流RTMP服务器,全平

TSINGSEE青犀视频官方技术博客 7007
上一篇: 如何搭建一个完整的视频直播系统?
下一篇: Android 使用Rtmp音视频推流(002)
STN_LCD
博客等级 码龄15年 384粉丝 54原创
评论 18
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值