1. 程式人生 > >Android視訊直播的實現(推流完整實現001)

Android視訊直播的實現(推流完整實現001)

最近一段時間,視訊直播可謂大火。在視訊直播領域,有不同的商家提供各種的商業解決方案,包括軟硬體裝置,攝像機,編碼器,流媒體伺服器等。本文要講解的是如何使用一系列免費工具,打造一套視訊直播方案。

視訊直播流程

視訊直播的流程可以分為如下幾步: 
採集 —>處理—>編碼和封裝—>推流到伺服器—>伺服器流分發—>播放器流播放

這裡寫圖片描述

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
            
           

相關推薦

Android視訊直播實現完整實現001

最近一段時間,視訊直播可謂大火。在視訊直播領域,有不同的商家提供各種的商業解決方案,包括軟硬體裝置,攝像機,編碼器,流媒體伺服器等。本文要講解的是如何使用一系列免費工具,打造一套視訊直播方案。 視訊直播流程 視訊直播的流程可以分為如下幾步:  採集 —>處理—>編碼和封裝—>

安卓 視訊直播二:端程式碼

想要從底層一步步寫起比較麻煩,需要了解一點影象處理的知識,為了快速開發,我選擇通過第三方的SDK,這裡簡單說一下第三方SDK,其中有騰訊,阿里,百度雲,網易,金山雲,抖音,大牛都支援不過各有利弊。 (1)騰訊雲ILVB實名認證後需要人工稽核5個工作日,反正至今沒有看到SDK; (2)阿里

[RTMP] 國內各大視訊直播CDN廠商搶流行為分析

# 背景 當存在一個推流客戶端正在向**rtmp://xxx.com/live/yyy**推流時,又有另外一個推流客戶端同時對這個地址進行推流,會發生什麼呢? 查閱了 [Adobe RTMP Spec](https://www.adobe.com/devnet/rtmp.html) 發現規範本身並未說明和定

android視訊的編輯錄製,裁剪,合成1

好久沒寫部落格了,最近的事情的比較多。公司正在向產品這塊轉型,要做音視訊的編輯開發,之前的接觸這塊的東西並不多,所以開發起來有很多的困難,從踩自定義相機的坑開始,視訊的錄製,編輯(主要包括合成和裁剪);音訊的錄製,裁剪;圖片的一些基本處理,包括裁剪,旋轉,新增文

Android直播視訊技術探究之---採集攝像頭Camera視訊源資料進行(採用金山雲SDK)

一、前言在之前已經詳細介紹了Android中的一種視訊資料來源:Camera,不瞭解的同學可以點選進入:Android中Camera使用詳解 ,在這篇文章中我們介紹瞭如何採集攝像頭的每一幀資料,然後進行

Android視訊直播實現包括伺服器搭建

http://blog.csdn.net/huaxun66/article/details/53427771 最近一段時間,視訊直播可謂大火。在視訊直播領域,有不同的商家提供各種的商業解決方案,包括軟硬體裝置,攝像機,編碼器,流媒體伺服器等。本文要講解的是如

Android視訊直播實現

最近一段時間,視訊直播可謂大火。在視訊直播領域,有不同的商家提供各種的商業解決方案,包括軟硬體裝置,攝像機,編碼器,流媒體伺服器等。本文要講解的是如何使用一系列免費工具,打造一套視訊直播方案。 視訊直播流程 視訊直播的流程可以分為如下幾步: 採集 —>處理—>

開源SDK實現Android視訊直播

最近一段時間,視訊直播可謂大火。在視訊直播領域,有不同的商家提供各種的商業解決方案,包括軟硬體裝置,攝像機,編碼器,流媒體伺服器等。本文要講解的是如何使用一系列免費工具,打造一套視訊直播方案。 視訊直播流程 視訊直播的流程可以分為如下幾步: 採集 —&

javaCV開發詳解之2:實現本地攝像頭視訊媒體伺服器以及攝像頭錄製視訊功能實現(基於javaCV-FFMPEG、javaCV-openCV)

javaCV系列文章: 補充篇: 歡迎大家積極開心的加入討論群 javacpp-ffmpeg: 前言: 本章將在上一章的基礎上,增加視訊推流到流媒體伺服器和視訊錄製的功能; 功能:實現邊播放邊錄製/推流,停止預覽即停止錄製/推流 提示:

關於Android直播那些小事-1

到年底了,閒暇之餘學了點熱門的東西,直播現在是很火的,所以我也玩玩直播,弄了個demo,準備玩一玩,這是第一篇,先說一下推流實現直播功能,陸續會有很多互動demo的功能,今天只是簡單的實現一下推流可以從攝像頭採集圖片到View上,這裡推流用的是Rtmp騰訊雲的工具。 1、今

day122:MoFang:OSSRS媒體直播伺服器&基於APICloud的acLive直播模組實現RTMP直播

目錄 1.docker安裝OSSRS流媒體直播伺服器 2.基於APICloud的acLive直播推流模組實現RTMP直播推流 3.直播流管理 1.docker安裝OSSRS流媒體直播伺服器 1.OSSRS簡介 在外界開發中, 如果要實現直播功能.常用的方式有: 1. 通過第三方介面來實現. 可以申請阿里雲

HTTP協議下可拖動時間軸播放FLV的實現媒體

prot pac -m method bytes encoding 編寫 時間軸 delay HTTP協議下實現FLV的播放其實並不復雜,當初實現的原理是使用了flowPlayer插件實現的,效果還不錯。但仍有兩大問題影響著客戶的訪問情緒: 1.預加載時頁面卡死,似乎沒有

Android 雙擊退出程序實現有側滑界面

是的 tro raw idt style tap sys .com ges 大家好,今天帶來雙擊退出程序實現方法,我知道,網上也是有許多關於雙擊退出程序實現的方法,所以,聽見當然是給大家帶來不一樣的雙擊退出的實現方法。 首先帶來的便是關於onKeyDown和onKe

Android音視頻通話過程中最小化成懸浮框的實現類似Android8.0畫中畫效果

apk 添加 touch null cas 如果 動態添加 int sta 關於音視頻通話過程中最小化成懸浮框這個功能的實現,網絡上類似的文章很多,但是好像還沒看到解釋的較為清晰的,這裏因為項目需要實現了這樣的一個功能,今天我把它記錄下來,一方面為了以後用到便於自己查閱,一

直播開發選在WEB端是選擇藉助應用直播還是flash直播

1、flash推流 優點:flash推流,不用下載其他推流軟體進行直播,可以直接開啟瀏覽器進行直播,簡化主播使用者操作。 缺點:flash將於2020年停止更新,當前應用flash直播的技術可能不再支援,需要做改動;目前市場上比較大的直播運營商:網易雲、阿里雲、騰訊雲、七牛雲、野狗,

一對一分析直播協議和技術

近年來直播已成為網際網路行業的大熱話題,直播答題、遊戲直播、競賽直播等層出不窮,直播早已成為人們耳熟能詳的技術。事實上直播的興起不僅與新時代人們要求為自己代言的心理有關,同時也得益於頻寬的提速和CDN技術的發展。 伴隨著CDN技術的成熟,企業自己部署雲伺服器做直播也越來越簡單 。 本文作

二叉樹的鏈式儲存結構及實現C語言完整程式碼+詳細註釋

鏈式儲存結構儲存二叉樹,實際上就是採用連結串列儲存二叉樹。 既然是使用連結串列,首先需要構建連結串列中節點的結構。考慮到儲存物件為二叉樹,其各個節點最多包含 3 部分,依次是:左孩子、節點資料和右孩子,因此,連結串列的每個節點都由這 3 部分組成: 圖 1 二叉連結串列結點構成 圖 1 中,Lchi

[媒體伺服器搭建] EasyDarwin伺服器搭建及客戶端完整示例

Windows下環境搭建一、準備工作,需要先下載我們需要的程式1、EasyDarwin  下載地址:https://github.com/EasyDarwin/EasyDarwin2、ffmpeg         下載地址:http://ffmpeg.org/3、nginx

【DSP】DSP5509A的FFT演算法實現附:完整程式碼及疑點解惑

傅立葉變換及FFT原理 說起傅立葉變換,每個人第一反應都是從時域轉換到頻域的手段,如下圖所示: 但除了這一點之外呢?原理呢,推導呢?大概都是一頭霧水…… 而FFT並不是一種新的變換,它是離散傅立葉變換(DFT)的一種快速演算法。 DFT的演算法速度: 由於我們在計算DF

Android直播開發之旅(7):Android視訊直播核心技術(架構)詳解

(轉載請宣告出處:http://blog.csdn.net/andrexpert/article/details/76919535) 一、直播架構解析      目前主流的直播架構中主要有兩種方案,即流媒體轉發、P2P。流媒體轉發,是一種在視訊直播中以流的方式將連續的音、