如何對RTSP播放器做功能和性能評估

好多開發者在做產品競品分析的時候,不知道如何界定一個RTSP播放器,大牛直播SDK認為,一個RTSP播放器,不是說有幾個類似于Open/Close接口就夠了,好的RTSP播放器需要具備以下功能和性能屬性:

1. 低延遲:大多數RTSP的播放都面向直播場景,所以,如果延遲過大,比如監控行業,小偷都走了,客戶端才看到,或者別人已經按過門鈴幾秒,主人才看到圖像,嚴重影響體驗,所以,低延遲是衡量一個好的RTSP播放器非常重要的指標,目前大牛直播SDK的RTSP播放延遲控制在幾百毫秒,VLC在幾秒,這個延遲,是長時間的低延遲,比如運行1天、一周、一個月甚至更久;

2. 音視頻同步或跳轉:有些開發者為了追求低延遲體驗,甚至不做音視頻同步,拿到audio video直接播放,導致a/v不同步,還有就是時間戳亂跳;

3. 支持多實例:一個好的播放器,需要支持同時播放多路音視頻數據,比如4-8-9-16-32窗口;

4. 支持buffer time設置:在一些有網絡抖動的場景,播放器需要支持精準的buffer time設置,一般來說,以毫秒計;

5. H.265的播放和錄制:除了H.264,還需要支持H.265,目前市面上的RTSP H.265攝像頭越來越多,支持H.265的RTSP播放器迫在眉睫,此外,單純的播放H.265還不夠,還需要可以能把H.265的數據能錄制下來;

6. TCP/UDP模式切換:考慮到好多服務器僅支持TCP或UDP模式,一個好的RTSP播放器需要支持TCP/UDP模式自動切換;

7. 靜音支持:比如,多窗口播放RTSP流,如果每個audio都播放出來,體驗非常不好,所以實時靜音功能非常必要;

8. 視頻view旋轉:好多攝像頭由于安裝限制,導致圖像倒置,所以一個好的RTSP播放器應該支持如視頻view實時旋轉(0° 90° 180° 270°)、水平反轉、垂直反轉;

9. 支持解碼后audio/video數據輸出(可選):大牛直播SDK接觸到好多開發者,希望能在播放的同時,獲取到YUV或RGB數據,進行人臉匹配等算法分析,所以音視頻回調可選;

10. 快照:感興趣或重要的畫面,實時截取下來非常必要;

11. 網絡抖動處理(如斷網重連):基本功能,不再贅述;

12. 跨平臺:一個好的播放器,跨平臺(Windows/Android/iOS)很有必要,起碼為了后續擴展性考慮,開發的時候,有這方面的考慮,目前大牛直播SDK的RTSP播放器,完美支持以上平臺;

13. 長期運行穩定性:提到穩定性,好多開發者不以為然,實際上,一個好的產品,穩定是最基本的前提,不容忽視!
14. 可以錄像:播放的過程中,隨時錄制下來感興趣的視頻片斷,存檔或其他二次處理;

15. log信息記錄:整體流程機制實時反饋,不多打log,但是不能一些重要的log,如播放過程中出錯等;

16. download速度實時反饋:可以看到實時下載速度反饋,以此來監聽網絡狀態;

17. 異常狀態處理:如播放的過程中,斷網、網絡抖動、來電話、切后臺后返回等各種場景的處理。

說了這么多,有開發者會反問,大牛直播SDK到底支持了哪些?以下做個簡單功能概述,如不單獨說明,系Windows、Android、iOS全平臺支持:

  • ?[支持播放協議]高穩定、超低延遲、業內首屈一指的RTSP直播播放器SDK;
  • ?[多實例播放]支持多實例播放;
  • ?[事件回調]支持網絡狀態、buffer狀態等回調;
  • ?[視頻格式]支持H.265、H.264,此外,Windows/Android平臺還支持RTSP MJPEG播放;
  • ?[音頻格式]支持AAC/PCMA/PCMU;
  • ?[H.264/H.265軟解碼]支持H.264/H.265軟解;
  • ?[H.264硬解碼]Android/iOS支持H.264硬解;
  • ?[H.265硬解]Android/iOS支持H.265硬解;
  • ?[H.264/H.265硬解碼]Android支持設置Surface模式硬解和普通模式硬解碼;
  • ?[RTSP模式設置]支持RTSP TCP/UDP模式設置;
  • ?[RTSP TCP/UDP自動切換]支持RTSP TCP、UDP模式自動切換;
  • ?[RTSP超時設置]支持RTSP超時時間設置,單位:秒;
  • ?[RTSP 401認證處理]支持上報RTSP 401事件,如URL攜帶鑒權信息,會自動處理;
  • ?[緩沖時間設置]支持buffer time設置;
  • ?[首屏秒開]支持首屏秒開模式;
  • ?[復雜網絡處理]支持斷網重連等各種網絡環境自動適配;
  • ?[快速切換URL]支持播放過程中,快速切換其他URL,內容切換更快;
  • ?[音視頻多種render機制]Android平臺,視頻:surfaceview/OpenGL ES,音頻:AudioTrack/OpenSL ES;
  • ?[實時靜音]支持播放過程中,實時靜音/取消靜音;
  • ?[實時快照]支持播放過程中截取當前播放畫面;
  • ?[渲染角度]支持0°,90°,180°和270°四個視頻畫面渲染角度設置;
  • ?[渲染鏡像]支持水平反轉、垂直反轉模式設置;
  • ?[實時下載速度更新]支持當前下載速度實時回調(支持設置回調時間間隔);
  • ?[解碼前視頻數據回調]支持H.264/H.265數據回調;
  • ?[解碼后視頻數據回調]支持解碼后YUV/RGB數據回調;
  • ?[解碼前音頻數據回調]支持AAC/PCMA/PCMU/SPEEX數據回調;
  • ?[音視頻自適應]支持播放過程中,音視頻信息改變后自適應;
  • ?[擴展錄像功能]完美支持和錄像SDK組合使用(支持RTSP H.265流錄制,支持PCMA/PCMU轉AAC后錄制,支持設置只錄制音頻或視頻)。

相關資料:Github:?https://github.com/daniulive/SmarterStreaming

基于AES加密的RTSP/RTMP多路轉發設計方案

很多開發者最近咨詢我們,除了我們Windows推送端采集編碼的音視頻數據可以加密外,其他RTSP/RTMP流如果想更安全的轉推到RTMP服務器或相應CDN改怎么辦?

實際上,我們在做RTMP整體加密方案的時候已經考慮到這種情況,SmartStreamRelayDemo在拉取RTSP或RTMP流,轉推RTMP的時候,可以選擇加密視頻,加密音頻或音視頻都加密,廢話不多說,參看代碼:

bool nt_stream_relay_wrapper::StartPush(const std::string& url)
{
    if ( is_pushing_ )
        return false;

    if ( url.empty() )
        return false;

    if ( !OpenPushHandle() )
        return false;

    auto push_handle = GetPushHandle();
    ASSERT(push_handle != nullptr);

    ASSERT(push_api_ != NULL);
    if ( NT_ERC_OK != push_api_->SetURL(push_handle, url.c_str(), NULL) )
    {
        if ( !is_started_rtsp_stream_ )
        {
            push_api_->Close(push_handle);
            SetPushHandle(nullptr);
        }

        return false;
    }

    // 加密測試 +++

    push_api_->SetRtmpEncryptionOption(push_handle, url.c_str(), 1, 1);

    NT_BYTE test_key[16] = {'1', '2', '3'};

    push_api_->SetRtmpEncryptionKey(push_handle, url.c_str(), test_key, 16);

    // 加密測試 --

    if ( NT_ERC_OK != push_api_->StartPublisher(push_handle, NULL) )
    {
        if ( !is_started_rtsp_stream_ )
        {
            push_api_->Close(push_handle);
            SetPushHandle(nullptr);
        }

        return false;
    }


    // // test push rtsp ++

    // push_api_->SetPushRtspTransportProtocol(push_handle, 1);
    // // push_api_->SetPushRtspTransportProtocol(push_handle, 2);

    // push_api_->SetPushRtspURL(push_handle, "rtsp://player.daniulive.com:554/liverelay111.sdp");

    // push_api_->StartPushRtsp(push_handle, 0);

    // // test push rtsp--

    is_pushing_ = true;

    return true;
}

上圖:

relay

這個時候,輸入轉發設置的Key(支持aes 128, aes 192, aes 256加密,即將發布SM4加密),方可正常播放音視頻數據。

此種方案的優勢在于基于AES音視頻逐幀數據加密音視頻數據,第三方即便是破解了URL,也沒法播放,通過抓包工具抓取到數據,也沒法正常顯示。

相關資料:Github:?https://github.com/daniulive/SmarterStreaming

Flutter下實現低延遲的跨平臺RTSP/RTMP播放

為什么要用Flutter?

Flutter是谷歌的移動UI框架,可以快速在iOS和Android上構建高質量的原生用戶界面。 Flutter可以與現有的代碼一起工作。在全世界,Flutter正在被越來越多的開發者和組織使用,并且Flutter是完全免費、開源的。

Flutter有哪些與眾不同

image

1. Beautiful – Flutter 允許你控制屏幕上的每一寸像素,這讓「設計」不用再對「實現」妥協;

2. Fast – 一個應用不卡頓的標準是什么,你可能會說 16ms 抑或是 60fps,這對桌面端應用或者移動端應用來說已足夠,但當面對廣闊的 AR/VR 領域,60fps 仍然會成為使人腦產生眩暈的瓶頸,而 Flutter 的目標遠不止 60fps;借助 Dart 支持的 AOT 編譯以及 Skia 的繪制,Flutter 可以運行的很快;

3. Productive – 前端開發可能已經習慣的開發中 hot reload 模式,但這一特性在移動開發中還算是個新鮮事。Flutter 提供有狀態的 hot reload 開發模式,并允許一套 codebase 運行于多端;其他的,再比如開發采用 JIT 編譯與發布的 AOT 編譯,都使得開發者在開發應用時可以更加高效;

4. Open – Dart / Skia / Flutter (Framework),這些都是開源的,Flutter 與 Dart 團隊也對包括 Web 在內的多種技術持開放態度,只要是優秀的他們都愿意借鑒吸收。而在生態建設上,Flutter 回應 GitHub Issue 的速度更是讓人驚嘆,因為是真的快(closed 狀態的 issue 平均解決時間為 0.29天);

除了支持APICloud, Unity3d, React Native外,大牛直播SDK為什么要做Flutter下的RTSP/RTMP播放器

首先,Flutter則是依靠Flutter Engine虛擬機在iOS和Android上運行,開發人員可以通過Flutter框架和API在內部進行交互。Flutter Engine使用C/C++編寫,具有低延遲輸入和高幀速率的特點,不像Unity3d一樣,我們是回調YUV/RGB數據,在Unity3d里面繪制,Flutter直接調用native SDK,效率更高。

其次,客戶和開發者驅動,Flutter發展至今,目前還沒有個像樣的RTSP或RTMP播放器,一個播放器,不是說,有個界面,有個開始、停止按鈕就可以了,一個好用的直播播放器,對功能和性能屬性要求很高,特別是穩定性和低延遲這塊,不謙虛的說,可能是首款功能強大、真正好用的Flutter RTSP/RTMP直播播放SDK

以大牛直播SDK的播放器為例:

RTMP直播播放器功能介紹:

  • [支持播放協議]高穩定、超低延遲(一秒內,行業內幾無效果接近的播放端)、業內首屈一指的RTMP直播播放器SDK;
  • [多實例播放]支持多實例播放;
  • [事件回調]支持網絡狀態、buffer狀態等回調;
  • [視頻格式]支持RTMP擴展H.265,H.264;
  • [音頻格式]支持AAC/PCMA/PCMU/Speex;
  • [H.264/H.265軟解碼]支持H.264/H.265軟解;
  • [H.264硬解碼]Android/iOS支持H.264硬解;
  • [H.265硬解]Android/iOS支持H.265硬解;
  • [H.264/H.265硬解碼]Android支持設置Surface模式硬解和普通模式硬解碼;
  • [緩沖時間設置]支持buffer time設置;
  • [首屏秒開]支持首屏秒開模式;
  • [低延遲模式]支持類似于線上娃娃機等直播方案的超低延遲模式設置(公網200~400ms);
  • [復雜網絡處理]支持斷網重連等各種網絡環境自動適配;
  • [快速切換URL]支持播放過程中,快速切換其他URL,內容切換更快;
  • [音視頻多種render機制]Android平臺,視頻:surfaceview/OpenGL ES,音頻:AudioTrack/OpenSL ES;
  • [實時靜音]支持播放過程中,實時靜音/取消靜音;
  • [實時快照]支持播放過程中截取當前播放畫面;
  • [渲染角度]支持0°,90°,180°和270°四個視頻畫面渲染角度設置;
  • [渲染鏡像]支持水平反轉、垂直反轉模式設置;
  • [實時下載速度更新]支持當前下載速度實時回調(支持設置回調時間間隔);
  • [解碼前視頻數據回調]支持H.264/H.265數據回調;
  • [解碼后視頻數據回調]支持解碼后YUV/RGB數據回調;
  • [解碼前音頻數據回調]支持AAC/PCMA/PCMU/SPEEX數據回調;
  • [音視頻自適應]支持播放過程中,音視頻信息改變后自適應;
  • [擴展錄像功能]完美支持和錄像SDK組合使用(支持RTMP擴展H.265流錄制,支持PCMA/PCMU/Speex轉AAC后錄制,支持設置只錄制音頻或視頻)

RTSP直播播放器功能介紹:

  • [支持播放協議]高穩定、超低延遲、業內首屈一指的RTSP直播播放器SDK;
  • [多實例播放]支持多實例播放;
  • [事件回調]支持網絡狀態、buffer狀態等回調;
  • [視頻格式]支持H.265、H.264,此外,Windows/Android平臺還支持RTSP MJPEG播放;
  • [音頻格式]支持AAC/PCMA/PCMU;
  • [H.264/H.265軟解碼]支持H.264/H.265軟解;
  • [H.264硬解碼]Android/iOS支持H.264硬解;
  • [H.265硬解]Android/iOS支持H.265硬解;
  • [H.264/H.265硬解碼]Android支持設置Surface模式硬解和普通模式硬解碼;
  • [RTSP模式設置]支持RTSP TCP/UDP模式設置;
  • [RTSP TCP/UDP自動切換]支持RTSP TCP、UDP模式自動切換;
  • [RTSP超時設置]支持RTSP超時時間設置,單位:秒;
  • [RTSP 401認證處理]支持上報RTSP 401事件,如URL攜帶鑒權信息,會自動處理;
  • [緩沖時間設置]支持buffer time設置;
  • [首屏秒開]支持首屏秒開模式;
  • [復雜網絡處理]支持斷網重連等各種網絡環境自動適配;
  • [快速切換URL]支持播放過程中,快速切換其他URL,內容切換更快;
  • [音視頻多種render機制]Android平臺,視頻:surfaceview/OpenGL ES,音頻:AudioTrack/OpenSL ES;
  • [實時靜音]支持播放過程中,實時靜音/取消靜音;
  • [實時快照]支持播放過程中截取當前播放畫面;
  • [渲染角度]支持0°,90°,180°和270°四個視頻畫面渲染角度設置;
  • [渲染鏡像]支持水平反轉、垂直反轉模式設置;
  • [實時下載速度更新]支持當前下載速度實時回調(支持設置回調時間間隔);
  • [解碼前視頻數據回調]支持H.264/H.265數據回調;
  • [解碼后視頻數據回調]支持解碼后YUV/RGB數據回調;
  • [解碼前音頻數據回調]支持AAC/PCMA/PCMU/SPEEX數據回調;
  • [音視頻自適應]支持播放過程中,音視頻信息改變后自適應;
  • [擴展錄像功能]完美支持和錄像SDK組合使用(支持RTSP H.265流錄制,支持PCMA/PCMU轉AAC后錄制,支持設置只錄制音頻或視頻)

Android和iOS手機上RTSP/RTMP播放效果:

1. 視頻播放效果:

http://www.iqiyi.com/w_19s8dv6yht.html

2. 界面截圖:

image

上接口:

//
//  smartplayer.dart
//  smartplayer
//
//  GitHub: https://github.com/daniulive/SmarterStreaming
//  website: http://www.nokunlock.com
//
//  Created by daniulive on 2019/02/25.
//  Copyright ? 2014~2019 daniulive. All rights reserved.
//

import 'dart:async';
import 'dart:convert';

import 'package:flutter/services.dart';

class EVENTID {
  static const EVENT_DANIULIVE_COMMON_SDK = 0x00000000;
  static const EVENT_DANIULIVE_PLAYER_SDK = 0x01000000;
  static const EVENT_DANIULIVE_PUBLISHER_SDK = 0x02000000;

  static const EVENT_DANIULIVE_ERC_PLAYER_STARTED =
      EVENT_DANIULIVE_PLAYER_SDK | 0x1;
  static const EVENT_DANIULIVE_ERC_PLAYER_CONNECTING =
      EVENT_DANIULIVE_PLAYER_SDK | 0x2;
  static const EVENT_DANIULIVE_ERC_PLAYER_CONNECTION_FAILED =
      EVENT_DANIULIVE_PLAYER_SDK | 0x3;
  static const EVENT_DANIULIVE_ERC_PLAYER_CONNECTED =
      EVENT_DANIULIVE_PLAYER_SDK | 0x4;
  static const EVENT_DANIULIVE_ERC_PLAYER_DISCONNECTED =
      EVENT_DANIULIVE_PLAYER_SDK | 0x5;
  static const EVENT_DANIULIVE_ERC_PLAYER_STOP =
      EVENT_DANIULIVE_PLAYER_SDK | 0x6;
  static const EVENT_DANIULIVE_ERC_PLAYER_RESOLUTION_INFO =
      EVENT_DANIULIVE_PLAYER_SDK | 0x7;
  static const EVENT_DANIULIVE_ERC_PLAYER_NO_MEDIADATA_RECEIVED =
      EVENT_DANIULIVE_PLAYER_SDK | 0x8;
  static const EVENT_DANIULIVE_ERC_PLAYER_SWITCH_URL =
      EVENT_DANIULIVE_PLAYER_SDK | 0x9;
  static const EVENT_DANIULIVE_ERC_PLAYER_CAPTURE_IMAGE =
      EVENT_DANIULIVE_PLAYER_SDK | 0xA;

  static const EVENT_DANIULIVE_ERC_PLAYER_RECORDER_START_NEW_FILE =
      EVENT_DANIULIVE_PLAYER_SDK | 0x21; /*錄像寫入新文件*/
  static const EVENT_DANIULIVE_ERC_PLAYER_ONE_RECORDER_FILE_FINISHED =
      EVENT_DANIULIVE_PLAYER_SDK | 0x22; /*一個錄像文件完成*/

  static const EVENT_DANIULIVE_ERC_PLAYER_START_BUFFERING =
      EVENT_DANIULIVE_PLAYER_SDK | 0x81;
  static const EVENT_DANIULIVE_ERC_PLAYER_BUFFERING =
      EVENT_DANIULIVE_PLAYER_SDK | 0x82;
  static const EVENT_DANIULIVE_ERC_PLAYER_STOP_BUFFERING =
      EVENT_DANIULIVE_PLAYER_SDK | 0x83;

  static const EVENT_DANIULIVE_ERC_PLAYER_DOWNLOAD_SPEED =
      EVENT_DANIULIVE_PLAYER_SDK | 0x91;
}

typedef SmartEventCallback = void Function(int, String, String, String);

class SmartPlayerController {
  MethodChannel _channel;
  EventChannel _eventChannel;
  SmartEventCallback _eventCallback;

  void init(int id) {
    _channel = MethodChannel('smartplayer_plugin_$id');
    _eventChannel = EventChannel('smartplayer_event_$id');
    _eventChannel.receiveBroadcastStream().listen(_onEvent, onError: _onError);
  }

  void setEventCallback(SmartEventCallback callback) {
    _eventCallback = callback;
  }

  void _onEvent(Object event) {
    if (event != null) {
      Map valueMap = json.decode(event);
      String param = valueMap['param'];
      onSmartEvent(param);
    }
  }

  void _onError(Object error) {
    // print('error:'+ error);
  }

  Future<dynamic> _smartPlayerCall(String funcName) async {
    var ret = await _channel.invokeMethod(funcName);
    return ret;
  }

  Future<dynamic> _smartPlayerCallInt(String funcName, int param) async {
    var ret = await _channel.invokeMethod(funcName, {
      'intParam': param,
    });
    return ret;
  }

  Future<dynamic> _smartPlayerCallIntInt(
      String funcName, int param1, int param2) async {
    var ret = await _channel.invokeMethod(funcName, {
      'intParam': param1,
      'intParam2': param2,
    });
    return ret;
  }

  Future<dynamic> _smartPlayerCallString(String funcName, String param) async {
    var ret = await _channel.invokeMethod(funcName, {
      'strParam': param,
    });
    return ret;
  }

  /// 設置解碼方式 false 軟解碼 true 硬解碼 默認為false
  /// </summary>
  /// <param name="isHwDecoder"></param>
  Future<dynamic> setVideoDecoderMode(int isHwDecoder) async {
    return _smartPlayerCallInt('setVideoDecoderMode', isHwDecoder);
  }

  /// <summary>
  /// 設置音頻輸出模式: if 0: 自動選擇; if with 1: audiotrack模式, 此接口僅限于Android平臺使用
  /// </summary>
  /// <param name="use_audiotrack"></param>
  Future<dynamic> setAudioOutputType(int useAudiotrack) async {
    return _smartPlayerCallInt('setAudioOutputType', useAudiotrack);
  }

  /// <summary>
  /// 設置播放端緩存大小, 默認200毫秒
  /// </summary>
  /// <param name="buffer"></param>
  Future<dynamic> setBuffer(int buffer) async {
    return _smartPlayerCallInt('setBuffer', buffer);
  }

  /// <summary>
  /// 接口可實時調用:設置是否實時靜音,1:靜音; 0: 取消靜音
  /// </summary>
  /// <param name="is_mute"></param>
  Future<dynamic> setMute(int isMute) async {
    return _smartPlayerCallInt('setMute', isMute);
  }

  /// <summary>
  /// 設置RTSP TCP模式, 1: TCP; 0: UDP
  /// </summary>
  /// <param name="is_using_tcp"></param>
  Future<dynamic> setRTSPTcpMode(int isUsingTcp) async {
    return _smartPlayerCallInt('setRTSPTcpMode', isUsingTcp);
  }

  /// <summary>
  /// 設置RTSP超時時間, timeout單位為秒,必須大于0
  /// </summary>
  /// <param name="timeout"></param>
  Future<dynamic> setRTSPTimeout(int timeout) async {
    return _smartPlayerCallInt('setRTSPTimeout', timeout);
  }

  /// <summary>
  /// 設置RTSP TCP/UDP自動切換
  /// 對于RTSP來說,有些可能支持rtp over udp方式,有些可能支持使用rtp over tcp方式.
  /// 為了方便使用,有些場景下可以開啟自動嘗試切換開關, 打開后如果udp無法播放,sdk會自動嘗試tcp, 如果tcp方式播放不了,sdk會自動嘗試udp.
  /// </summary>
  /// <param name="is_auto_switch_tcp_udp"></param>
  Future<dynamic> setRTSPAutoSwitchTcpUdp(int is_auto_switch_tcp_udp) async {
    return _smartPlayerCallInt('setRTSPAutoSwitchTcpUdp', is_auto_switch_tcp_udp);
  }

  /// <summary>
  /// 設置快速啟動該模式,
  /// </summary>
  /// <param name="is_fast_startup"></param>
  Future<dynamic> setFastStartup(int isFastStartup) async {
    return _smartPlayerCallInt('setFastStartup', isFastStartup);
  }

  /// <summary>
  /// 設置超低延遲模式 false不開啟 true開啟 默認false
  /// </summary>
  /// <param name="mode"></param>
  Future<dynamic> setPlayerLowLatencyMode(int mode) async {
    return _smartPlayerCallInt('setPlayerLowLatencyMode', mode);
  }

  /// <summary>
  /// 設置視頻垂直反轉
  /// </summary>
  /// <param name="is_flip"></param>
  Future<dynamic> setFlipVertical(int is_flip) async {
    return _smartPlayerCallInt('setFlipVertical', is_flip);
  }

  /// <summary>
  /// 設置視頻水平反轉
  /// </summary>
  /// <param name="is_flip"></param>
  Future<dynamic> setFlipHorizontal(int is_flip) async {
    return _smartPlayerCallInt('setFlipHorizontal', is_flip);
  }

  /// <summary>
  /// 設置順時針旋轉, 注意除了0度之外, 其他角度都會額外消耗性能
  /// degress: 當前支持 0度,90度, 180度, 270度 旋轉
  /// </summary>
  /// <param name="degress"></param>
  Future<dynamic> setRotation(int degress) async {
    return _smartPlayerCallInt('setRotation', degress);
  }

  /// <summary>
  /// 設置是否回調下載速度
  /// is_report: if 1: 上報下載速度, 0: 不上報.
  /// report_interval: 上報間隔,以秒為單位,>0.
  /// </summary>
  /// <param name="is_report"></param>
  /// <param name="report_interval"></param>
  Future<dynamic> setReportDownloadSpeed(
      int isReport, int reportInterval) async {
    return _smartPlayerCallIntInt(
        'setReportDownloadSpeed', isReport, reportInterval);
  }

  /// <summary>
  /// Set playback orientation(設置播放方向),此接口僅適用于Android平臺
  /// </summary>
  /// <param name="surOrg"></param>
  /// surOrg: current orientation,  PORTRAIT 1, LANDSCAPE with 2
  Future<dynamic> setOrientation(int surOrg) async {
    return _smartPlayerCallInt('setOrientation', surOrg);
  }

  /// <summary>
  /// 設置是否需要在播放或錄像過程中快照
  /// </summary>
  /// <param name="is_save_image"></param>
  Future<dynamic> setSaveImageFlag(int isSaveImage) async {
    return _smartPlayerCallInt('setSaveImageFlag', isSaveImage);
  }

  /// <summary>
  /// 播放或錄像過程中快照
  /// </summary>
  /// <param name="imageName"></param>
  Future<dynamic> saveCurImage(String imageName) async {
    return _smartPlayerCallString('saveCurImage', imageName);
  }

  /// <summary>
  /// 播放或錄像過程中,快速切換url
  /// </summary>
  /// <param name="uri"></param>
  Future<dynamic> switchPlaybackUrl(String uri) async {
    return _smartPlayerCallString('switchPlaybackUrl', uri);
  }

  /// <summary>
  /// 創建錄像存儲路徑
  /// </summary>
  /// <param name="path"></param>
  Future<dynamic> createFileDirectory(String path) async {
    return _smartPlayerCallString('createFileDirectory', path);
  }

  /// <summary>
  /// 設置錄像存儲路徑
  /// </summary>
  /// <param name="path"></param>
  Future<dynamic> setRecorderDirectory(String path) async {
    return _smartPlayerCallString('setRecorderDirectory', path);
  }

  /// <summary>
  /// 設置單個錄像文件大小
  /// </summary>
  /// <param name="size"></param>
  Future<dynamic> setRecorderFileMaxSize(int size) async {
    return _smartPlayerCallInt('setRecorderFileMaxSize', size);
  }

  /// <summary>
  /// 設置錄像時音頻轉AAC編碼的開關
  /// aac比較通用,sdk增加其他音頻編碼(比如speex, pcmu, pcma等)轉aac的功能.
  /// </summary>
  /// <param name="is_transcode"></param>
  /// is_transcode: 設置為1的話,如果音頻編碼不是aac,則轉成aac,如果是aac,則不做轉換. 設置為0的話,則不做任何轉換. 默認是0.
  Future<dynamic> setRecorderAudioTranscodeAAC(int is_transcode) async {
    return _smartPlayerCallInt('setRecorderAudioTranscodeAAC', is_transcode);
  }

  /// <summary>
  /// 設置播放路徑
  /// </summary>
  Future<dynamic> setUrl(String url) async {
    return _smartPlayerCallString('setUrl', url);
  }

  /// <summary>
  /// 開始播放
  /// </summary>
  Future<dynamic> startPlay() async {
    return _smartPlayerCall('startPlay');
  }

  /// <summary>
  /// 停止播放
  /// </summary>
  Future<dynamic> stopPlay() async {
    return _smartPlayerCall('stopPlay');
  }

  /// <summary>
  /// 開始錄像
  /// </summary>
  Future<dynamic> startRecorder() async {
    return _smartPlayerCall('startRecorder');
  }

  /// <summary>
  /// 停止錄像
  /// </summary>
  Future<dynamic> stopRecorder() async {
    return _smartPlayerCall('stopRecorder');
  }

  /// <summary>
  /// 關閉播放
  /// </summary>
  Future<dynamic> dispose() async {
    return await _channel.invokeMethod('dispose');
  }

  void onSmartEvent(String param) {
    if (!param.contains(",")) {
      print("[onNTSmartEvent] android傳遞參數錯誤");
      return;
    }

    List<String> strs = param.split(',');

    String code = strs[1];
    String param1 = strs[2];
    String param2 = strs[3];
    String param3 = strs[4];
    String param4 = strs[5];

    int evCode = int.parse(code);

    var p1, p2, p3;
    switch (evCode) {
      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_STARTED:
        print("開始。。");
        break;
      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTING:
        print("連接中。。");
        break;
      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTION_FAILED:
        print("連接失敗。。");
        break;
      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTED:
        print("連接成功。。");
        break;
      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_DISCONNECTED:
        print("連接斷開。。");
        break;
      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_STOP:
        print("停止播放。。");
        break;
      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_RESOLUTION_INFO:
        print("分辨率信息: width: " + param1 + ", height: " + param2);
        p1 = param1;
        p2 = param2;
        break;
      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_NO_MEDIADATA_RECEIVED:
        print("收不到媒體數據,可能是url錯誤。。");
        break;
      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_SWITCH_URL:
        print("切換播放URL。。");
        break;

      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_CAPTURE_IMAGE:
        print("快照: " + param1 + " 路徑:" + param3);

        if (int.parse(param1) == 0) {
           print("截取快照成功。.");
        } else {
           print("截取快照失敗。.");
        }
        p1 = param1;
        p2 = param3;
        break;

      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_RECORDER_START_NEW_FILE:
        print("[record]開始一個新的錄像文件 : " + param3);
        p3 = param3;
        break;
      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_ONE_RECORDER_FILE_FINISHED:
        print("[record]已生成一個錄像文件 : " + param3);
        p3 = param3;
        break;

      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_START_BUFFERING:
        print("Start_Buffering");
        break;

      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_BUFFERING:
        print("Buffering: " + param1 + "%");
        p1 = param1;
        break;

      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_STOP_BUFFERING:
        print("Stop_Buffering");
        break;

      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_DOWNLOAD_SPEED:
         print("download_speed:" + (double.parse(param1) * 8 / 1000).toStringAsFixed(0) + "kbps" + ", " + (double.parse(param1) / 1024).toStringAsFixed(0) + "KB/s");
        p1 = param1;
        break;
    }
    if (_eventCallback != null) {
      _eventCallback(evCode, p1, p2, p3);
    }
  }
}

image.gif

調用實例:

//
//  main.dart
//  main
//
//  GitHub: https://github.com/daniulive/SmarterStreaming
//  website: http://www.nokunlock.com
//
//  Created by daniulive on 2019/02/25.
//  Copyright ? 2014~2019 daniulive. All rights reserved.
//

import 'dart:io';
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:smartplayer_native_view/smartplayer.dart';
import 'package:smartplayer_native_view/smartplayer_plugin.dart';

void main() {
  ///
  /// 強制豎屏
  ///
  SystemChrome.setPreferredOrientations(
      [DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);

  runApp(new MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => new _MyAppState();
}

class _MyAppState extends State<MyApp> {
  SmartPlayerController player;
  double aspectRatio = 4.0 / 3.0;

  //輸入需要播放的RTMP/RTSP url
  TextEditingController playback_url_controller_ = TextEditingController();

  //Event事件回調顯示
  TextEditingController event_controller_ = TextEditingController();

  bool is_playing_ = false;
  bool is_mute_ = false;

  var rotate_degrees_ = 0;

  Widget smartPlayerView() {
      return SmartPlayerWidget(
        onSmartPlayerCreated: onSmartPlayerCreated,
      );
    }

  @override
  void initState() {
     print("initState called..");
    super.initState();
  }

  @override
  void didChangeDependencies() {
    print('didChangeDependencies called..');
    super.didChangeDependencies();
  }

  @override
  void deactivate() {
    print('deactivate called..');
    super.deactivate();
  }

  @override
  void dispose() {
    print("dispose called..");
    player.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
          appBar: AppBar(
            title: const Text('Flutter SmartPlayer Demo'),
          ),
          body: new SingleChildScrollView(
            child: new Column(
              children: <Widget>[
                new Container(
                  color: Colors.black,
                  child: AspectRatio(
                    child: smartPlayerView(),
                    aspectRatio: aspectRatio,
                  ),
                ),
                new TextField(
                  controller: playback_url_controller_,
                  keyboardType: TextInputType.text,
                  decoration: InputDecoration(
                    contentPadding: EdgeInsets.all(10.0),
                    icon: Icon(Icons.link),
                    labelText: '請輸入RTSP/RTMP url',
                  ),
                  autofocus: false,
                ),
                new Row(
                  children: [
                    new RaisedButton(
                        onPressed: this.onSmartPlayerStartPlay,
                        child: new Text("開始播放")),
                    new Container(width: 20),
                    new RaisedButton(
                        onPressed: this.onSmartPlayerStopPlay,
                        child: new Text("停止播放")),
                    new Container(width: 20),
                    new RaisedButton(
                        onPressed: this.onSmartPlayerMute,
                        child: new Text("實時靜音")),
                  ],
                ),
                new Row(
                  children: [
                    new RaisedButton(
                        onPressed: this.onSmartPlayerSwitchUrl,
                        child: new Text("實時切換URL")),
                    new Container(width: 20),
                    new RaisedButton(
                        onPressed: this.onSmartPlayerSetRotation,
                        child: new Text("實時旋轉View")),
                  ],
                ),
                new TextField(
                  controller: event_controller_,
                  keyboardType: TextInputType.text,
                  decoration: InputDecoration(
                    contentPadding: EdgeInsets.all(10.0),
                    icon: Icon(Icons.event_note),
                    labelText: 'Event狀態回調',
                  ),
                  autofocus: false,
                ),
              ],
            ),
          )),
    );
  }

  void _eventCallback(int code, String param1, String param2, String param3) {
    String event_str;

    switch (code) {
      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_STARTED:
        event_str = "開始..";
        break;
      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTING:
        event_str = "連接中..";
        break;
      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTION_FAILED:
        event_str = "連接失敗..";
        break;
      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_CONNECTED:
        event_str = "連接成功..";
        break;
      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_DISCONNECTED:
        event_str = "連接斷開..";
        break;
      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_STOP:
        event_str = "停止播放..";
        break;
      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_RESOLUTION_INFO:
        event_str = "分辨率信息: width: " + param1 + ", height: " + param2;
        setState(() {
          aspectRatio = double.parse(param1) / double.parse(param2);
          print('change aspectRatio:$aspectRatio');
        });
        break;
      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_NO_MEDIADATA_RECEIVED:
        event_str = "收不到媒體數據,可能是url錯誤..";
        break;
      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_SWITCH_URL:
        event_str = "切換播放URL..";
        break;
      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_CAPTURE_IMAGE:
        event_str = "快照: " + param1 + " 路徑: " + param3;

        if (int.parse(param1) == 0) {
          print("截取快照成功。.");
        } else {
          print("截取快照失敗。.");
        }
        break;
      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_RECORDER_START_NEW_FILE:
        event_str = "[record] new file: " + param3;
        break;
      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_ONE_RECORDER_FILE_FINISHED:
        event_str = "[record] record finished: " + param3;
        break;
      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_START_BUFFERING:
        //event_str = "Start Buffering";
        break;
      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_BUFFERING:
        event_str = "Buffering: " + param1 + "%";
        break;
      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_STOP_BUFFERING:
        //event_str = "Stop Buffering";
        break;
      case EVENTID.EVENT_DANIULIVE_ERC_PLAYER_DOWNLOAD_SPEED:
        event_str = "download_speed:" +
            (double.parse(param1) * 8 / 1000).toStringAsFixed(0) +
            "kbps" +
            ", " +
            (double.parse(param1) / 1024).toStringAsFixed(0) +
            "KB/s";
        break;
    }

    event_controller_.text = event_str;
  }

  void onSmartPlayerCreated(SmartPlayerController controller) async {
    player = controller;
    player.setEventCallback(_eventCallback);

    var ret = -1;

    //設置video decoder模式
    var is_video_hw_decoder = 0;
    if (defaultTargetPlatform == TargetPlatform.android)
    {
      ret = await player.setVideoDecoderMode(is_video_hw_decoder);
    }
    else if(defaultTargetPlatform == TargetPlatform.iOS)
    {
      is_video_hw_decoder = 1;
      ret = await player.setVideoDecoderMode(is_video_hw_decoder);
    }

    //設置緩沖時間
    var play_buffer = 100;
    ret = await player.setBuffer(play_buffer);

    //設置快速啟動
    var is_fast_startup = 1;
    ret = await player.setFastStartup(is_fast_startup);

    //是否開啟低延遲模式
    var is_low_latency_mode = 0;
    ret = await player.setPlayerLowLatencyMode(is_low_latency_mode);

    //set report download speed(默認5秒一次回調 用戶可自行調整report間隔)
    ret = await player.setReportDownloadSpeed(1, 2);

    //設置RTSP超時時間
        var rtsp_timeout = 10;
      ret = await player.setRTSPTimeout(rtsp_timeout);

    var is_auto_switch_tcp_udp = 1;
        ret = await player.setRTSPAutoSwitchTcpUdp(is_auto_switch_tcp_udp);

    // 設置RTSP TCP模式
        //ret = await player.setRTSPTcpMode(1);

    //第一次啟動 為方便測試 設置個初始url
    playback_url_controller_.text = "rtmp://live.hkstv.hk.lxdns.com/live/hks2";
  }

  Future<void> onSmartPlayerStartPlay() async {
    var ret = -1;

    if (playback_url_controller_.text.length < 8) {
      playback_url_controller_.text =
          "rtmp://live.hkstv.hk.lxdns.com/live/hks1"; //給個初始url
    }

    //實時靜音設置 
    ret = await player.setMute(is_mute_ ? 1 : 0);

    if (!is_playing_) {
      ret = await player.setUrl(playback_url_controller_.text);
      ret = await player.startPlay();

      if (ret == 0) {
        is_playing_ = true;
      }
    }
  }

  Future<void> onSmartPlayerStopPlay() async {
    if (is_playing_) {
      await player.stopPlay();
      playback_url_controller_.clear();
      is_playing_ = false;
      is_mute_ = false;
    }
  }

  Future<void> onSmartPlayerMute() async {
    if (is_playing_) {
      is_mute_ = !is_mute_;
      await player.setMute(is_mute_ ? 1 : 0);
    }
  }

  Future<void> onSmartPlayerSwitchUrl() async {
    if (is_playing_) {
      if (playback_url_controller_.text.length < 8) {
        playback_url_controller_.text =
            "rtmp://live.hkstv.hk.lxdns.com/live/hks1";
      }

      await player.switchPlaybackUrl(playback_url_controller_.text);
    }
  }

  Future<void> onSmartPlayerSetRotation() async {
    if (is_playing_) {
      rotate_degrees_ += 90;
      rotate_degrees_ = rotate_degrees_ % 360;

      if (0 == rotate_degrees_) {
        print("旋轉90度");
      } else if (90 == rotate_degrees_) {
        print("旋轉180度");
      } else if (180 == rotate_degrees_) {
        print("旋轉270度");
      } else if (270 == rotate_degrees_) {
        print("不旋轉");
      }

      await player.setRotation(rotate_degrees_);
    }
  }
}

image.gif

經測試,Flutter環境下,RTMP和RTSP播放,擁有Native SDK一樣優異的播放體驗。

相關資料:Github:?https://github.com/daniulive/SmarterStreaming

QQ群:

5張圖看懂如何實現Windows RTMP實時導播功能

一直以來,好多開發者苦于如何實現RTMP導播數據源實時切換,以下是大牛直播SDK導播切換說明,支持只切換數據源模式,或音視頻混音合成輸出模式:

數據源:

1. rtmp/rtsp音視頻流;

2. 本地屏幕/攝像頭/音頻數據;

3.本地flv文件。

輸出:

1. 多路流合成一路流后,推送到RTMP服務器;

2. 多路合成后的流,支持本地錄像、快照。

使用說明:

無視頻合成/音頻混音模式:

1. 打開SmartStreamRelayDemo.exe,輸入一路RTMP或RTSP流,在拉流地址輸入需要轉推的RTMP的url,如“rtmp://player.daniulive.com:1935/hls/stream666”,先點擊“拉流”,再點擊“推流”按鈕,如需本地預覽,可以點擊“預覽”按鈕。

打開SmartPlayer.exe,輸入剛剛設置的RTMP url:rtmp://player.daniulive.com:1935/hls/stream666,點擊“播放”即可。

如下圖所示:

?2. 切換一路RTMP數據源,輸入新的RTMP地址,點擊“切換拉流地址”即可:

?

3. 切換一路RTSP數據源,輸入新的RTSP地址,點擊“切換拉流地址”即可:

?

視頻合成/音頻混音模式:

1. 合流界面:

?

2. 播放輸出界面:

?

細心的你會發現,三路流分辨率和協議封裝不同,不過依然可自動切換,從而實現播放端觀眾無感知的導播體驗。

技術優勢:

1. 以SDK形式輸出,企業或開發者可根據需求完成多樣化的產品需求;

2. 行業內接口更靈活,資源占用更低;

3. 超低延遲輸出,效率更高;

4. 支持合流后的圖像預覽;

5. 支持合流后的音頻混音;

6. 支持導播過程中,隨時切斷某一路音視頻或音頻;

7. 豈止是合流,還可以實時錄像、快照等,接口更豐富。

相關資料和測試程序下載:

Github:?https://github.com/daniulive/SmarterStreaming

官網:http://www.nokunlock.com

rtmp/rtsp/hls公網測試地址

相信大家在調試播放器的時候,都有這樣的困惑,很難找到合適的公有測試源,以下是大牛直播整理的真正可用的直播地址源。

其中,rtmp和rtsp的url,用https://github.com/daniulive/SmarterStreaming提供的播放器驗證通過。

hls的地址,用vlc驗證通過。

1. RTMP協議直播源

香港衛視:rtmp://live.hkstv.hk.lxdns.com/live/hks

2. RTSP協議直播源

大熊兔(VOD):rtsp://184.72.239.149/vod/mp4://BigBuckBunny_175k.mov

國外電視臺:rtsp://rtsp-v3-spbtv.msk.spbtv.com/spbtv_v3_1/214_110.sdp

3. HTTP協議直播源

香港衛視:http://live.hkstv.hk.lxdns.com/live/hks/playlist.m3u8

污污直播app-污污直播破解版永久免费版