From 7c9f88be0bc86aba3c0143d2dc7a4f98df123cd9 Mon Sep 17 00:00:00 2001 From: winlin Date: Tue, 18 Jan 2022 12:04:15 +0800 Subject: [PATCH] Eliminate unused *.as files for Adobe Flash. v5.0.22 --- trunk/doc/CHANGELOG.md | 1 + .../players/srs_bwt/.actionScriptProperties | 41 - trunk/research/players/srs_bwt/.project | 17 - .../players/srs_bwt/release/srs_bwt.swf | Bin 7180 -> 0 bytes .../players/srs_bwt/src/SrsBandwidth.as | 576 -- .../players/srs_bwt/src/srs.bandwidth.js | 234 - trunk/research/players/srs_bwt/src/srs_bwt.as | 186 - .../srs_player/.actionScriptProperties | 44 - trunk/research/players/srs_player/.project | 17 - .../org.eclipse.core.resources.prefs | 3 - .../players/srs_player/release/srs_player.swf | Bin 52087 -> 0 bytes .../players/srs_player/src/HlsNetStream.as | 5374 ----------------- .../research/players/srs_player/src/Player.as | 210 - .../players/srs_player/src/Utility.as | 51 - .../players/srs_player/src/srs_player.as | 668 -- .../srs_publisher/.actionScriptProperties | 40 - trunk/research/players/srs_publisher/.project | 17 - .../org.eclipse.core.resources.prefs | 3 - .../srs_publisher/release/srs_publisher.swf | Bin 5641 -> 0 bytes .../srs_publisher/src/srs_publisher.as | 462 -- .../srs_reuse_conn/.actionScriptProperties | 41 - .../research/players/srs_reuse_conn/.project | 17 - .../org.eclipse.core.resources.prefs | 3 - .../players/srs_reuse_conn/FlashCS5UI.swc | Bin 121293 -> 0 bytes .../srs_reuse_conn/release/srs_reuse_conn.swf | Bin 27452 -> 0 bytes .../srs_reuse_conn/src/srs_reuse_conn.as | 123 - trunk/src/core/srs_core_version5.hpp | 2 +- 27 files changed, 2 insertions(+), 8128 deletions(-) delete mode 100755 trunk/research/players/srs_bwt/.actionScriptProperties delete mode 100755 trunk/research/players/srs_bwt/.project delete mode 100755 trunk/research/players/srs_bwt/release/srs_bwt.swf delete mode 100755 trunk/research/players/srs_bwt/src/SrsBandwidth.as delete mode 100755 trunk/research/players/srs_bwt/src/srs.bandwidth.js delete mode 100755 trunk/research/players/srs_bwt/src/srs_bwt.as delete mode 100755 trunk/research/players/srs_player/.actionScriptProperties delete mode 100755 trunk/research/players/srs_player/.project delete mode 100755 trunk/research/players/srs_player/.settings/org.eclipse.core.resources.prefs delete mode 100644 trunk/research/players/srs_player/release/srs_player.swf delete mode 100755 trunk/research/players/srs_player/src/HlsNetStream.as delete mode 100644 trunk/research/players/srs_player/src/Player.as delete mode 100644 trunk/research/players/srs_player/src/Utility.as delete mode 100755 trunk/research/players/srs_player/src/srs_player.as delete mode 100755 trunk/research/players/srs_publisher/.actionScriptProperties delete mode 100755 trunk/research/players/srs_publisher/.project delete mode 100755 trunk/research/players/srs_publisher/.settings/org.eclipse.core.resources.prefs delete mode 100755 trunk/research/players/srs_publisher/release/srs_publisher.swf delete mode 100755 trunk/research/players/srs_publisher/src/srs_publisher.as delete mode 100755 trunk/research/players/srs_reuse_conn/.actionScriptProperties delete mode 100755 trunk/research/players/srs_reuse_conn/.project delete mode 100644 trunk/research/players/srs_reuse_conn/.settings/org.eclipse.core.resources.prefs delete mode 100644 trunk/research/players/srs_reuse_conn/FlashCS5UI.swc delete mode 100644 trunk/research/players/srs_reuse_conn/release/srs_reuse_conn.swf delete mode 100644 trunk/research/players/srs_reuse_conn/src/srs_reuse_conn.as diff --git a/trunk/doc/CHANGELOG.md b/trunk/doc/CHANGELOG.md index 837d5180f..2b73287c5 100644 --- a/trunk/doc/CHANGELOG.md +++ b/trunk/doc/CHANGELOG.md @@ -8,6 +8,7 @@ The changelog for SRS. ## SRS 5.0 Changelog +* v5.0, 2022-01-18, Eliminate unused *.as files for Adobe Flash. v5.0.22 * v5.0, 2022-01-13, Switch LICENSE from MIT to **MIT or MulanPSL-2.0**. v5.0.21 * v5.0, 2021-10-24, For [#2689](https://github.com/ossrs/srs/issues/2689): Support loongarch, loongson CPU. v5.0.19 * v5.0, 2021-10-17, Support daemon(fork twice) for Darwin/OSX [ST#23](https://github.com/ossrs/state-threads/issues/23). v5.0.18 diff --git a/trunk/research/players/srs_bwt/.actionScriptProperties b/trunk/research/players/srs_bwt/.actionScriptProperties deleted file mode 100755 index 1a4d9fa33..000000000 --- a/trunk/research/players/srs_bwt/.actionScriptProperties +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/trunk/research/players/srs_bwt/.project b/trunk/research/players/srs_bwt/.project deleted file mode 100755 index 240a737f9..000000000 --- a/trunk/research/players/srs_bwt/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - srs_bwt - - - - - - com.adobe.flexbuilder.project.flexbuilder - - - - - - com.adobe.flexbuilder.project.actionscriptnature - - diff --git a/trunk/research/players/srs_bwt/release/srs_bwt.swf b/trunk/research/players/srs_bwt/release/srs_bwt.swf deleted file mode 100755 index d9c9330c479bd526e715b7bf434188711db1b728..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7180 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 OzF&{6|9=4;N(RX#iZkf| diff --git a/trunk/research/players/srs_bwt/src/SrsBandwidth.as b/trunk/research/players/srs_bwt/src/SrsBandwidth.as deleted file mode 100755 index 6ffd7dcf7..000000000 --- a/trunk/research/players/srs_bwt/src/SrsBandwidth.as +++ /dev/null @@ -1,576 +0,0 @@ -// -// Copyright (c) 2013-2021 Winlin -// -// SPDX-License-Identifier: MIT -// -package -{ - import flash.events.NetStatusEvent; - import flash.external.ExternalInterface; - import flash.net.NetConnection; - import flash.net.NetStream; - import flash.net.ObjectEncoding; - import flash.utils.clearTimeout; - import flash.utils.setTimeout; - - /** - * SRS bandwidth check/test library, - * user can copy this file and use it directly, - * this library will export as callback functions, and js callback functions. - * - * Usage: - * var bandwidth:SrsBandwidth = new SrsBandwidth(); - * bandwidth.initialize(......); // required - * bandwidth.check_bandwidth(......); // required - * bandwidth.stop(); // optional - * - * @remark we donot use event, but use callback functions set by initialize. - */ - public class SrsBandwidth - { - /** - * server notice client to do the downloading/play bandwidth test. - */ - public static const StatusSrsBwtcPlayStart:String = "srs.bwtc.play.start"; - /** - * server notice client to complete the downloading/play bandwidth test. - */ - public static const StatusSrsBwtcPlayStop:String = "srs.bwtc.play.stop"; - /** - * server notice client to do the uploading/publish bandwidth test. - */ - public static const StatusSrsBwtcPublishStart:String = "srs.bwtc.publish.start"; - /** - * server notice client to complete the uploading/publish bandwidth test. - */ - public static const StatusSrsBwtcPublishStop:String = "srs.bwtc.publish.stop"; - - /** - * constructor, do nothing - */ - public function SrsBandwidth() - { - } - - /** - * initialize the bandwidth test tool, the callbacks. null to ignore. - * - * the as callbacks. - * @param as_on_ready, function():void, callback when bandwidth tool is ready to run. - * @param as_on_status_change, function(code:String, data:String):void, where: - * code can be: - * "NetConnection.Connect.Failed", see NetStatusEvent(evt.info.code). - * "NetConnection.Connect.Rejected", see NetStatusEvent(evt.info.code). - * "NetConnection.Connect.Success", see NetStatusEvent(evt.info.code). - * "NetConnection.Connect.Closed", see NetStatusEvent(evt.info.code). - * SrsBandwidth.StatusSrsBwtcPlayStart, "srs.bwtc.play.start", when srs start test play bandwidth. - * SrsBandwidth.StatusSrsBwtcPlayStop, "srs.bwtc.play.stop", when srs complete test play bandwidth. - * SrsBandwidth.StatusSrsBwtcPublishStart, "srs.bwtc.publish.start", when srs start test publish bandwidth. - * SrsBandwidth.StatusSrsBwtcPublishStop, "srs.bwtc.publish.stop", when srs complete test publish bandwidth. - * data is extra parameter: - * kbps, for code is SrsBandwidth.StatusSrsBwtcPlayStop or SrsBandwidth.StatusSrsBwtcPublishStop. - * "", otherwise empty string. - * @param as_on_progress_change, function(percent:Number):void, where: - * percent, the progress percent, 0 means 0%, 100 means 100%. - * @param as_on_srs_info, function(srs_server:String, srs_primary:String, srs_authors:String, srs_id:String, srs_pid:String, srs_server_ip:String):void, where: - * srs_server: the srs server info. - * srs_primary: the srs primary authors info. - * srs_authors: the srs authors info. - * srs_id: the tracable log id, to direclty grep the log. - * srs_pid: the srs process id, to direclty grep the log. - * srs_server_ip: the srs server ip, where client connected at. - * @param as_on_complete, function(start_time:Number, end_time:Number, play_kbps:Number, publish_kbps:Number, play_bytes:Number, publish_bytes:Number, play_time:Number, publish_time:Number):void, where - * start_time, the start timestamp, in ms. - * end_time, the finish timestamp, in ms. - * play_kbps, the play/downloading kbps. - * publish_kbps, the publish/uploading kbps. - * play_bytes, the bytes play/download from server, in bytes. - * publish_bytes, the bytes publish/upload to server, in bytes. - * play_time, the play/download duration time, in ms. - * publish_time, the publish/upload duration time, in ms. - * - * the js callback id. - * @param js_id, specifies the id of swfobject, used to identify the bandwidth object. - * for all js callback, the first param always be the js_id, to identify the callback object. - * - * the js callbacks. - * @param js_on_ready, function(js_id:String):void, callback when bandwidth tool is ready to run. - * @param js_on_status_change, function(js_id:String, code:String, data:String):void - * @param as_on_progress_change, function(js_id:String, percent:Number):void - * @param as_on_srs_info, function(js_id:String, srs_server:String, srs_primary:String, srs_authors:String, srs_id:String, srs_pid:String, srs_server_ip:String):void - * @param as_on_complete, function(js_id:String, start_time:Number, end_time:Number, play_kbps:Number, publish_kbps:Number, play_bytes:Number, publish_bytes:Number, play_time:Number, publish_time:Number):void - * - * the js export functions. - * @param js_export_check_bandwidth, function(url:String):void, for js to start bandwidth check, @see: check_bandwidth(url:String):void - * @param js_export_stop, function():void, for js to stop bandwidth check, @see: stop():void - * - * @remark, all parameters can be null. - * @remark, as and js callback use same parameter, except that the js calblack first parameter is js_id:String. - */ - public function initialize( - as_on_ready:Function, as_on_status_change:Function, as_on_progress_change:Function, as_on_srs_info:Function, as_on_complete:Function, - js_id:String, js_on_ready:String, js_on_status_change:String, js_on_progress_change:String, js_on_srs_info:String, js_on_complete:String, - js_export_check_bandwidth:String, js_export_stop:String - ):void { - this.as_on_ready = as_on_ready; - this.as_on_srs_info = as_on_srs_info; - this.as_on_status_change = as_on_status_change; - this.as_on_progress_change = as_on_progress_change; - this.as_on_complete = as_on_complete; - - this.js_id = js_id; - this.js_on_srs_info = js_on_srs_info; - this.js_on_ready = js_on_ready; - this.js_on_status_change = js_on_status_change; - this.js_on_progress_change = js_on_progress_change; - this.js_on_complete = js_on_complete; - - this.js_export_check_bandwidth = js_export_check_bandwidth; - this.js_export_stop = js_export_stop; - - flash.utils.setTimeout(this.system_on_js_ready, 0); - } - - /** - * start check bandwidth. - * @param url, a String indicates the url to check bandwidth, - * format as: rtmp://server:port/app?key=xxx&&vhost=xxx - * for example, rtmp://dev:1935/app?key=35c9b402c12a7246868752e2878f7e0e&vhost=bandcheck.srs.com - * where the key and vhost must be config in SRS, like: - * vhost bandcheck.srs.com { - * enabled on; - * chunk_size 65000; - * bandcheck { - * enabled on; - * key "35c9b402c12a7246868752e2878f7e0e"; - * interval 30; - * limit_kbps 4000; - * } - * } - * - * @remark user must invoke this as method, or js exported method. - */ - public function check_bandwidth(url:String):void { - this.js_call_check_bandwidth(url); - } - - /** - * stop check bancwidth. - * @remark it's optional, however, user can abort the bandwidth check. - */ - public function stop():void { - this.js_call_stop(); - } - - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////// /////////////////////////////// - ////////////////////////// /////////////////////////////// - ////////////////////////// /////////////////////////////// - ////////////////////////// /////////////////////////////// - ////////////////////////// Private Section, ignore please. /////////////////////////////// - ////////////////////////// /////////////////////////////// - ////////////////////////// /////////////////////////////// - ////////////////////////// /////////////////////////////// - ////////////////////////// /////////////////////////////// - ////////////////////////// /////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * *********************************************************************** - * private section, including private fields, method and embeded classes. - * *********************************************************************** - */ - - /** - * as callback. - */ - private var as_on_ready:Function; - private var as_on_srs_info:Function; - private var as_on_status_change:Function; - private var as_on_progress_change:Function; - private var as_on_complete:Function; - - /** - * js callback. - */ - private var js_id:String; - private var js_on_ready:String; - private var js_on_srs_info:String; - private var js_on_status_change:String; - private var js_on_progress_change:String; - private var js_on_complete:String; - - /** - * js export functions. - */ - private var js_export_check_bandwidth:String; - private var js_export_stop:String; - - /** - * srs debug infos - */ - private var srs_server:String = null; - private var srs_primary:String = null; - private var srs_authors:String = null; - private var srs_id:String = null; - private var srs_pid:String = null; - private var srs_server_ip:String = null; - - /** - * the underlayer connection, to send call message to do the bandwidth - * check/test with server. - */ - private var connection:NetConnection = null; - // for bms4, use stream to play then do bandwidth test. - private var stream:NetStream = null; - - /** - * use timeout to sendout publish call packets. - * when got stop publish packet from server, stop publish call loop. - */ - private var publish_timeout_handler:uint = 0; - - /** - * system callack event, when js ready, register callback for js. - * the actual main function. - */ - private function system_on_js_ready():void { - if (!flash.external.ExternalInterface.available) { - log("js not ready, try later."); - flash.utils.setTimeout(this.system_on_js_ready, 100); - return; - } - - if (this.js_export_check_bandwidth != null) { - flash.external.ExternalInterface.addCallback(this.js_export_check_bandwidth, this.js_call_check_bandwidth); - } - if (this.js_export_stop != null) { - flash.external.ExternalInterface.addCallback(this.js_export_stop, this.js_call_stop); - } - - if (as_on_ready != null) { - as_on_ready(); - } - if (js_on_ready != null) { - flash.external.ExternalInterface.call(this.js_on_ready, this.js_id); - } - } - private function js_call_check_bandwidth(url:String):void { - js_call_stop(); - - __on_progress_change(0); - - // init connection - log("create connection for bandwidth check"); - connection = new NetConnection; - connection.objectEncoding = ObjectEncoding.AMF0; - connection.client = { - onStatus: onStatus, - // play - onSrsBandCheckStartPlayBytes: onSrsBandCheckStartPlayBytes, - onSrsBandCheckPlaying: onSrsBandCheckPlaying, - onSrsBandCheckStopPlayBytes: onSrsBandCheckStopPlayBytes, - // publish - onSrsBandCheckStartPublishBytes: onSrsBandCheckStartPublishBytes, - onSrsBandCheckStopPublishBytes: onSrsBandCheckStopPublishBytes, - onSrsBandCheckFinished: onSrsBandCheckFinished - }; - connection.addEventListener(NetStatusEvent.NET_STATUS, onStatus); - connection.connect(url); - - __on_progress_change(3); - } - private function js_call_stop():void { - if (connection) { - connection.close(); - connection = null; - } - } - - /** - * NetConnection callback this function, when recv server call "onSrsBandCheckStartPlayBytes" - * then start @updatePlayProgressTimer for updating the progressbar - * */ - private function onSrsBandCheckStartPlayBytes(evt:Object):void{ - var duration_ms:Number = evt.duration_ms; - var interval_ms:Number = evt.interval_ms; - log("start play test, duration=" + duration_ms + ", interval=" + interval_ms); - - connection.call("onSrsBandCheckStartingPlayBytes", null); - __on_status_change(SrsBandwidth.StatusSrsBwtcPlayStart); - - __on_progress_change(10); - } - private function onSrsBandCheckPlaying(evt:Object):void{ - } - private function onSrsBandCheckStopPlayBytes(evt:Object):void{ - var duration_ms:Number = evt.duration_ms; - var interval_ms:Number = evt.interval_ms; - var duration_delta:Number = evt.duration_delta; - var bytes_delta:Number = evt.bytes_delta; - - var kbps:Number = 0; - if(duration_delta > 0){ - kbps = bytes_delta * 8.0 / duration_delta; // b/ms == kbps - } - kbps = (int(kbps * 10))/10.0; - - flash.utils.setTimeout(stopPlayTest, 0); - __on_status_change(SrsBandwidth.StatusSrsBwtcPlayStop, String(kbps)); - - __on_progress_change(40); - } - private function stopPlayTest():void{ - connection.call("onSrsBandCheckStoppedPlayBytes", null); - } - /** - * publishing methods. - */ - private function onSrsBandCheckStartPublishBytes(evt:Object):void{ - var duration_ms:Number = evt.duration_ms; - var interval_ms:Number = evt.interval_ms; - - connection.call("onSrsBandCheckStartingPublishBytes", null); - - flash.utils.setTimeout(publisher, 0); - __on_status_change(SrsBandwidth.StatusSrsBwtcPublishStart); - - __on_progress_change(60); - } - private function publisher():void{ - var data:Array = new Array(); - - /** - * the data size cannot too large, it will increase the test time. - * server need atleast got one packet, then timeout to stop the publish. - * - * cannot too small neither, it will limit the max publish kbps. - * - * the test values: - * test_s test_s - * data_size max_publish_kbps (no limit) (limit upload to 5KBps) - * 100 2116 6.5 7.3 - * 200 4071 6.5 7.7 - * 300 6438 6.5 10.3 - * 400 9328 6.5 10.2 - * 500 10377 6.5 10.0 - * 600 13737 6.5 10.8 - * 700 15635 6.5 12.0 - * 800 18103 6.5 14.0 - * 900 20484 6.5 14.2 - * 1000 21447 6.5 16.8 - */ - var data_size:int = 900; - for(var i:int; i < data_size; i++) { - data.push("SrS band check data from client's publishing......"); - } - - connection.call("onSrsBandCheckPublishing", null, data); - - publish_timeout_handler = flash.utils.setTimeout(publisher, 0); - } - private function onSrsBandCheckStopPublishBytes(evt:Object):void{ - var duration_ms:Number = evt.duration_ms; - var interval_ms:Number = evt.interval_ms; - var duration_delta:Number = evt.duration_delta; - var bytes_delta:Number = evt.bytes_delta; - - var kbps:Number = 0; - if(duration_delta > 0){ - kbps = bytes_delta * 8.0 / duration_delta; // b/ms == kbps - } - kbps = (int(kbps * 10))/10.0; - - stopPublishTest(); - - __on_progress_change(90); - } - private function stopPublishTest():void{ - // the stop publish response packet can not send out, for the queue is full. - //connection.call("onSrsBandCheckStoppedPublishBytes", null); - - // clear the timeout to stop the send loop. - if (publish_timeout_handler > 0) { - flash.utils.clearTimeout(publish_timeout_handler); - publish_timeout_handler = 0; - } - } - private function onSrsBandCheckFinished(evt:Object):void{ - var start_time:Number = evt.start_time; - var end_time:Number = evt.end_time; - var play_kbps:Number = evt.play_kbps; - var publish_kbps:Number = evt.publish_kbps; - var play_bytes:Number = evt.play_bytes; - var play_time:Number = evt.play_time; - var publish_bytes:Number = evt.publish_bytes; - var publish_time:Number = evt.publish_time; - - if (this.as_on_complete != null) { - this.as_on_complete(start_time, end_time, play_kbps, publish_kbps, play_bytes, publish_bytes, play_time, publish_time); - } - if (this.js_on_complete != null) { - flash.external.ExternalInterface.call(this.js_on_complete, this.js_id, - start_time, end_time, play_kbps, publish_kbps, play_bytes, publish_bytes, play_time, publish_time); - } - - __on_progress_change(100); - - // when got finish packet, directly close connection. - js_call_stop(); - - // the last final packet can not send out, for the queue is full. - //connection.call("finalClientPacket", null); - } - - /** - * get NetConnection NetStatusEvent - */ - private function onStatus(evt:NetStatusEvent): void { - log(evt.info.code); - - var srs_version:String = null; - if (evt.info.hasOwnProperty("data") && evt.info.data) { - if (evt.info.data.hasOwnProperty("srs_server")) { - srs_server = evt.info.data.srs_server; - } - if (evt.info.data.hasOwnProperty("srs_primary")) { - srs_primary = evt.info.data.srs_primary; - } - if (evt.info.data.hasOwnProperty("srs_authors")) { - srs_authors = evt.info.data.srs_authors; - } - if (evt.info.data.hasOwnProperty("srs_id")) { - srs_id = evt.info.data.srs_id; - } - if (evt.info.data.hasOwnProperty("srs_pid")) { - srs_pid = evt.info.data.srs_pid; - } - if (evt.info.data.hasOwnProperty("srs_server_ip")) { - srs_server_ip = evt.info.data.srs_server_ip; - } - if (evt.info.data.hasOwnProperty("srs_version")) { - srs_version = evt.info.data.srs_version; - } - - if (this.as_on_srs_info != null) { - this.as_on_srs_info(srs_server, srs_primary, srs_authors, srs_id, srs_pid, srs_server_ip); - } - if (this.js_on_srs_info != null) { - flash.external.ExternalInterface.call(this.js_on_srs_info, this.js_id, - srs_server, srs_primary, srs_authors, srs_id, srs_pid, srs_server_ip); - } - } - - var e:NetStatusEvent = evt; - var foo:Function = function():void{ - var evt:NetStatusEvent = e; - if (evt.info.code) { - __on_status_change(evt.info.code); - } - switch(evt.info.code){ - case "NetConnection.Connect.Success": - __on_progress_change(8); - break; - } - }; - foo(); - - // for bms4, play stream to trigger the bandwidth check. - if (evt.info.code != "NetConnection.Connect.Success") { - return; - } - if (stream != null) { - return; - } - - var is_bms:Boolean = false; - if (srs_server.indexOf("BMS/") == 0 || srs_server.indexOf("UPYUN/") == 0) { - is_bms = true; - } - if (parseInt(srs_version.charAt(0)) >= 4 && is_bms) { - stream = new NetStream(connection); - stream.addEventListener(NetStatusEvent.NET_STATUS, function(evt:NetStatusEvent):void{ - log(evt.info.code); - - if (evt.info.code == "NetStream.Play.Start") { - } - }); - stream.play("test"); - log("play stream for " + srs_server + " " + srs_version); - return; - } - } - - /** - * invoke the callback. - */ - private function __on_progress_change(percent:Number):void { - if (this.as_on_progress_change != null) { - this.as_on_progress_change(percent); - } - if (this.js_on_progress_change != null) { - flash.external.ExternalInterface.call(this.js_on_progress_change, this.js_id, - percent); - } - } - private function __on_status_change(code:String, data:String=""):void { - if (this.as_on_status_change != null) { - this.as_on_status_change(code, data); - } - if (this.js_on_status_change != null) { - flash.external.ExternalInterface.call(this.js_on_status_change, this.js_id, - code, data); - } - } - - private function log(msg:String):void { - trace(msg); - if (ExternalInterface.available) { - ExternalInterface.call("console.log", msg); - } - } - } -} diff --git a/trunk/research/players/srs_bwt/src/srs.bandwidth.js b/trunk/research/players/srs_bwt/src/srs.bandwidth.js deleted file mode 100755 index ce24b9430..000000000 --- a/trunk/research/players/srs_bwt/src/srs.bandwidth.js +++ /dev/null @@ -1,234 +0,0 @@ -// -// Copyright (c) 2013-2021 Winlin -// -// SPDX-License-Identifier: MIT -// -/** -* the SrsBandwidth library for js to do bandwidth test. -* @param container the html container id. -* @param width a float value specifies the width of bandwidth. -* @param height a float value specifies the height of bandwidth. -* @param private_object [optional] an object that used as private object, -* for example, the logic chat object which owner this bandwidth. -* Usage: - var bandwidth = new SrsBandwidth("container_id", 100, 1); - bandwidth.on_bandwidth_ready = function() { - // auto start check bandwidth when tool is ready. - this.check_bandwidth(url); - } - bandwidth.on_update_progress = function(percent) { - // console.log(percent + "%"); - } - bandwidth.on_update_status = function(status) { - // console.log(status); - } - bandwidth.on_srs_info = function(srs_server, srs_primary, srs_authors, srs_id, srs_pid, srs_server_ip) { - // console.log( - // "server:" + srs_server + ", primary:" + srs_primary + ", authors:" + srs_authors + - // ", srs_id:" + srs_id + ", srs_pid:" + srs_pid + ", ip:" + srs_server_ip - //); - } - bandwidth.render("rtmp://dev:1935/app?key=35c9b402c12a7246868752e2878f7e0e&vhost=bandcheck.srs.com"); -* where the HTML page must contains an element: -
-* this js will directly erase the container by swfobject. -*/ -function SrsBandwidth(container, width, height, private_object) { - if (!SrsBandwidth.__id) { - SrsBandwidth.__id = 100; - } - if (!SrsBandwidth.__bandwidths) { - SrsBandwidth.__bandwidths = []; - } - - SrsBandwidth.__bandwidths.push(this); - - this.private_object = private_object; - this.container = container; - this.width = width; - this.height = height; - this.id = SrsBandwidth.__id++; - this.stream_url = null; - this.callbackObj = null; - - // the callback set data. - this.percent = 0; - this.status = ""; - this.server = ""; - this.completed = false; -} -/** -* user can set some callback, then start the bandwidth. -* @param url the bandwidth test url. -* callbacks: -* on_bandwidth_ready():void, when srs bandwidth ready, user can play. -* on_update_progress(percent:Number):void, when srs bandwidth update the progress. -* percent:Number 100 means 100%. -* on_update_status(status:String):void, when srs bandwidth update the status. -* status:String the human readable status text. -*/ -SrsBandwidth.prototype.render = function(url) { - if (url) { - this.stream_url = url; - } - - // embed the flash. - var flashvars = {}; - flashvars.id = this.id; - flashvars.on_bandwidth_ready = "__srs_on_bandwidth_ready"; - flashvars.on_update_progress = "__srs_on_update_progress"; - flashvars.on_update_status = "__srs_on_update_status"; - flashvars.on_srs_info = "__srs_on_srs_info"; - flashvars.on_complete = "__srs_on_complete"; - - var params = {}; - params.wmode = "opaque"; - params.allowFullScreen = "true"; - params.allowScriptAccess = "always"; - - var attributes = {}; - - var self = this; - - swfobject.embedSWF( - "srs_bwt/release/srs_bwt.swf?_version="+srs_get_version_code(), - this.container, - this.width, this.height, - "11.1.0", "js/AdobeFlashbandwidthInstall.swf", - flashvars, params, attributes, - function(callbackObj){ - self.callbackObj = callbackObj; - } - ); - - return this; -} -/** -* play the stream. -* @param stream_url the url of stream, rtmp or http. -*/ -SrsBandwidth.prototype.check_bandwidth = function(url) { - this.stop(); - SrsBandwidth.__bandwidths.push(this); - - // the callback set data. - this.percent = 0; - this.status = ""; - this.server = ""; - this.completed = false; - - if (url) { - this.stream_url = url; - } - - this.callbackObj.ref.__check_bandwidth(this.stream_url); -} -SrsBandwidth.prototype.stop = function(url) { - for (var i = 0; i < SrsBandwidth.__bandwidths.length; i++) { - var bandwidth = SrsBandwidth.__bandwidths[i]; - - if (bandwidth.id != this.id) { - continue; - } - - SrsBandwidth.__bandwidths.splice(i, 1); - break; - } - - this.callbackObj.ref.__stop(); -} -SrsBandwidth.prototype.on_bandwidth_ready = function() { -} -SrsBandwidth.prototype.on_update_progress = function(percent) { -} -SrsBandwidth.prototype.on_update_status = function(status) { -} -SrsBandwidth.prototype.on_srs_info = function(srs_server, srs_primary, srs_authors, srs_id, srs_pid, srs_server_ip) { -} -SrsBandwidth.prototype.on_complete = function(start_time, end_time, play_kbps, publish_kbps, play_bytes, publish_bytes, play_time, publish_time) { -} -SrsBandwidth.prototype.on_error = function(code) { -} -function __srs_find_bandwidth(id) { - for (var i = 0; i < SrsBandwidth.__bandwidths.length; i++) { - var bandwidth = SrsBandwidth.__bandwidths[i]; - - if (bandwidth.id != id) { - continue; - } - - return bandwidth; - } - - throw new Error("bandwidth not found. id=" + id); -} -function __srs_on_bandwidth_ready(id) { - var bandwidth = __srs_find_bandwidth(id); - bandwidth.on_bandwidth_ready(); -} -function __srs_on_update_progress(id, percent) { - var bandwidth = __srs_find_bandwidth(id); - bandwidth.percent = percent; - bandwidth.on_update_progress(percent); -} -function __srs_on_update_status(id, code, data) { - var bandwidth = __srs_find_bandwidth(id); - - var status = ""; - switch(code){ - case "NetConnection.Connect.Failed": - if (bandwidth.completed) { - return; - } - bandwidth.on_error(code); - status = "连接服务器失败!"; - break; - case "NetConnection.Connect.Rejected": - if (bandwidth.completed) { - return; - } - bandwidth.completed = true; - bandwidth.on_update_progress(100); - bandwidth.on_error(code); - status = "服务器拒绝连接,测速过于频繁!"; - break; - case "NetConnection.Connect.Success": - status = "连接服务器成功!"; - break; - case "NetConnection.Connect.Closed": - if (bandwidth.completed) { - return; - } - bandwidth.on_error(code); - status = "连接已断开!"; - break; - case "srs.bwtc.play.start": - status = "开始测试下行带宽"; - break; - case "srs.bwtc.play.stop": - bandwidth.completed = true; - status = "下行带宽测试完毕," + data + "kbps,开始测试上行带宽。"; - break; - default: - return; - } - - bandwidth.status = status; - bandwidth.on_update_status(status); -} -function __srs_on_srs_info(id, srs_server, srs_primary, srs_authors, srs_id, srs_pid, srs_server_ip) { - var bandwidth = __srs_find_bandwidth(id); - bandwidth.status = status; - bandwidth.server = srs_server_ip; - bandwidth.on_srs_info(srs_server, srs_primary, srs_authors, srs_id, srs_pid, srs_server_ip); -} -function __srs_on_complete(id, start_time, end_time, play_kbps, publish_kbps, play_bytes, publish_bytes, play_time, publish_time) { - var bandwidth = __srs_find_bandwidth(id); - - var status = "检测结束: " + bandwidth.server + " 上行: " + publish_kbps + " kbps" + " 下行: " + play_kbps + " kbps" - + " 测试时间: " + Number((end_time - start_time) / 1000).toFixed(1) + " 秒"; - bandwidth.status = status; - bandwidth.on_update_status(status); - - bandwidth.on_complete(start_time, end_time, play_kbps, publish_kbps, play_bytes, publish_bytes, play_time, publish_time); -} diff --git a/trunk/research/players/srs_bwt/src/srs_bwt.as b/trunk/research/players/srs_bwt/src/srs_bwt.as deleted file mode 100755 index eb59e1df4..000000000 --- a/trunk/research/players/srs_bwt/src/srs_bwt.as +++ /dev/null @@ -1,186 +0,0 @@ -// -// Copyright (c) 2013-2021 Winlin -// -// SPDX-License-Identifier: MIT -// -package -{ - import flash.display.LoaderInfo; - import flash.display.Sprite; - import flash.display.StageAlign; - import flash.display.StageScaleMode; - import flash.events.Event; - import flash.events.NetStatusEvent; - import flash.events.TimerEvent; - import flash.external.ExternalInterface; - import flash.net.NetConnection; - import flash.net.ObjectEncoding; - import flash.system.System; - import flash.ui.ContextMenu; - import flash.ui.ContextMenuItem; - import flash.utils.Timer; - import flash.utils.setTimeout; - - public class srs_bwt extends Sprite - { - /** - * the SRS bandwidth check/test library object. - */ - private var bandwidth:SrsBandwidth = new SrsBandwidth(); - - /** - * when not specifies any param, directly run the swf. - */ - private var default_url:String = "rtmp://dev:1935/app?key=35c9b402c12a7246868752e2878f7e0e&vhost=bandcheck.srs.com"; - - public function srs_bwt() - { - if (!this.stage) { - this.addEventListener(Event.ADDED_TO_STAGE, this.system_on_add_to_stage); - } else { - this.system_on_add_to_stage(null); - } - } - private function system_on_add_to_stage(evt:Event):void { - this.stage.scaleMode = StageScaleMode.NO_SCALE; - this.stage.align = StageAlign.TOP_LEFT; - - // init context menu - var myMenu:ContextMenu = new ContextMenu(); - myMenu.hideBuiltInItems(); - myMenu.customItems.push(new ContextMenuItem("SRS带宽测试工具", true)); - this.contextMenu = myMenu; - - check_bandwidth(); - } - - private function check_bandwidth():void { - // closure - var self:srs_bwt = this; - - ///////////////////////////////////////////////////////////////////// - // initialize the bandwidth check/test library - ///////////////////////////////////////////////////////////////////// - // js callback, set to null if ignore. - var conf:Object = this.root.loaderInfo.parameters; - var js_id:String = conf.id? conf.id:null; - var js_on_ready:String = conf.on_bandwidth_ready? conf.on_bandwidth_ready:null; - var js_on_srs_info:String = conf.on_srs_info? conf.on_srs_info:null; - var js_on_progress_change:String = conf.on_update_progress? conf.on_update_progress:null; - var js_on_status_change:String = conf.on_update_status? conf.on_update_status:null; - var js_on_complete:String = conf.on_complete? conf.on_complete:null; - - // js export, set to null to disable - var js_export_check_bandwidth:String = "__check_bandwidth"; - var js_export_stop:String = "__stop"; - - // as callback, set to null if ignore. - var as_on_ready:Function = function():void { - self.on_ready(); - }; - var as_on_status_change:Function = function(code:String, data:String):void { - self.on_status_change(code, data); - }; - var as_on_progress_change:Function = function(percent:Number):void { - self.on_progress(percent); - }; - var as_on_srs_info:Function = function(srs_server:String, srs_primary:String, srs_authors:String, srs_id:String, srs_pid:String, srs_server_ip:String):void { - self.update_context_items(srs_server, srs_primary, srs_authors, srs_id, srs_pid, srs_server_ip); - }; - var as_on_complete:Function = function(start_time:Number, end_time:Number, play_kbps:Number, publish_kbps:Number, play_bytes:Number, publish_bytes:Number, play_time:Number, publish_time:Number):void { - self.on_complete(start_time, end_time, play_kbps, publish_kbps, play_bytes, publish_bytes, play_time, publish_time); - }; - - bandwidth.initialize( - as_on_ready, as_on_status_change, as_on_progress_change, as_on_srs_info, as_on_complete, - js_id, js_on_ready, js_on_status_change, js_on_progress_change, js_on_srs_info, js_on_complete, - js_export_check_bandwidth, js_export_stop - ); - ///////////////////////////////////////////////////////////////////// - } - - private function on_ready():void { - var conf:Object = this.root.loaderInfo.parameters; - - // for directly run swf. - if (!conf.id) { - log("directly run swf, load default url: " + this.default_url); - this.bandwidth.check_bandwidth(this.default_url); - } - - } - private function on_progress(percent:Number):void { - log("progress:" + percent + "%"); - } - private function update_context_items( - srs_server:String, srs_primary:String, srs_authors:String, - srs_id:String, srs_pid:String, srs_server_ip:String - ):void { - // for context menu - var customItems:Array = [new ContextMenuItem("SrsPlayer")]; - if (srs_server != null) { - customItems.push(new ContextMenuItem("Server: " + srs_server)); - } - if (srs_primary != null) { - customItems.push(new ContextMenuItem("Primary: " + srs_primary)); - } - if (srs_authors != null) { - customItems.push(new ContextMenuItem("Authors: " + srs_authors)); - } - if (srs_server_ip != null) { - customItems.push(new ContextMenuItem("SrsIp: " + srs_server_ip)); - } - if (srs_pid != null) { - customItems.push(new ContextMenuItem("SrsPid: " + srs_pid)); - } - if (srs_id != null) { - customItems.push(new ContextMenuItem("SrsId: " + srs_id)); - } - contextMenu.customItems = customItems; - } - public function on_status_change(code:String, data:String): void { - log(code); - switch(code){ - case "NetConnection.Connect.Failed": - log("连接服务器失败!"); - break; - case "NetConnection.Connect.Rejected": - log("服务器拒绝连接!"); - break; - case "NetConnection.Connect.Success": - log("连接服务器成功!"); - break; - case SrsBandwidth.StatusSrsBwtcPlayStart: - log("开始测试下行带宽"); - break; - case SrsBandwidth.StatusSrsBwtcPlayStop: - log("下行带宽测试完毕," + data + "kbps,开始测试上行带宽。"); - break; - case SrsBandwidth.StatusSrsBwtcPublishStart: - log("开始测试上行带宽"); - break; - case SrsBandwidth.StatusSrsBwtcPublishStop: - log("上行带宽测试完毕," + data + "kbps,"); - break; - case "NetConnection.Connect.Closed": - log("连接已断开!"); - break; - } - } - private function on_complete( - start_time:Number, end_time:Number, play_kbps:Number, publish_kbps:Number, - play_bytes:Number, publish_bytes:Number, play_time:Number, publish_time:Number - ):void { - var status:String = "检测结束: 上行: " + publish_kbps + " kbps" + " 下行: " + play_kbps + " kbps" - + " 测试时间: " + Number((end_time - start_time) / 1000).toFixed(1) + " 秒"; - log(status); - } - - private function log(msg:String):void { - trace(msg); - if (ExternalInterface.available) { - ExternalInterface.call("console.log", msg); - } - } - } -} diff --git a/trunk/research/players/srs_player/.actionScriptProperties b/trunk/research/players/srs_player/.actionScriptProperties deleted file mode 100755 index 1908de474..000000000 --- a/trunk/research/players/srs_player/.actionScriptProperties +++ /dev/null @@ -1,44 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/trunk/research/players/srs_player/.project b/trunk/research/players/srs_player/.project deleted file mode 100755 index 87ed06163..000000000 --- a/trunk/research/players/srs_player/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - srs_player - - - - - - com.adobe.flexbuilder.project.flexbuilder - - - - - - com.adobe.flexbuilder.project.actionscriptnature - - diff --git a/trunk/research/players/srs_player/.settings/org.eclipse.core.resources.prefs b/trunk/research/players/srs_player/.settings/org.eclipse.core.resources.prefs deleted file mode 100755 index fc698988d..000000000 --- a/trunk/research/players/srs_player/.settings/org.eclipse.core.resources.prefs +++ /dev/null @@ -1,3 +0,0 @@ -#Wed Dec 18 10:07:19 CST 2013 -eclipse.preferences.version=1 -encoding/=utf-8 diff --git a/trunk/research/players/srs_player/release/srs_player.swf b/trunk/research/players/srs_player/release/srs_player.swf deleted file mode 100644 index 3294a43a4371c6fd3d9a4752d06ac053af266ca3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52087 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

S%$SZ0o#b6jl)@Tv!s_jXa(Sf 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 0) { - var uv:URLVariables = new URLVariables(uri.substr(uri.indexOf("?") + 1)); - var obj:Object = {}; - for (var k:String in uv) { - var v:String = uv[k]; - if (k == "shp_sip1") { - obj.srs_server_ip = v; - } else if (k == "shp_cid") { - obj.srs_id = v; - } else if (k == "shp_pid") { - obj.srs_pid = v; - } - //log("uv[" + k + "]=" + v); - } - - // ignore when not changed. - if (!metadata || metadata.srs_server_ip != obj.srs_server_ip || metadata.srs_id != obj.srs_id || metadata.srs_pid != obj.srs_pid) { - if (client && client.hasOwnProperty("onMetaData")) { - log("got metadata for url " + uri); - client.onMetaData(obj); - } - } - metadata = obj; - } - - download(uri, function(stream:ByteArray):void{ - log("got ts seqno=" + parsed_ts_seq_no + ", " + stream.length + " bytes"); - - var flv:FlvPiece = new FlvPiece(parsed_ts_seq_no); - var body:ByteArray = new ByteArray(); - stream.position = 0; - hls.parseBodyAsync(flv, stream, body, function():void{ - body.position = 0; - //log("ts parsed, seqno=" + parsed_ts_seq_no + ", flv=" + body.length + "B"); - onFlvBody(uri, body); - - parsed_ts_seq_no++; - setTimeout(refresh_ts, 0); - }); - }); - } - private function download(uri:String, completed:Function):void { - // http get. - var url:URLStream = new URLStream(); - var stream:ByteArray = new ByteArray(); - - url.addEventListener(ProgressEvent.PROGRESS, function(evt:ProgressEvent):void { - if (url.bytesAvailable <= 0) { - return; - } - - //log(uri + " total=" + evt.bytesTotal + ", loaded=" + evt.bytesLoaded + ", available=" + url.bytesAvailable); - var bytes:ByteArray = new ByteArray(); - url.readBytes(bytes, 0, url.bytesAvailable); - stream.writeBytes(bytes); - }); - - url.addEventListener(Event.COMPLETE, function(evt:Event):void { - log(uri + " completed, total=" + stream.length + "bytes"); - if (url.bytesAvailable <= 0) { - completed(stream); - return; - } - - //log(uri + " completed" + ", available=" + url.bytesAvailable); - var bytes:ByteArray = new ByteArray(); - url.readBytes(bytes, 0, url.bytesAvailable); - stream.writeBytes(bytes); - - completed(stream); - }); - - url.addEventListener(IOErrorEvent.IO_ERROR, function(evt:IOErrorEvent):void{ - onPlayFailed(evt); - }); - - url.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function(evt:SecurityErrorEvent):void{ - onPlayRejected(evt); - }); - - // we set to the query. - uri += ((uri.indexOf("?") == -1)? "?":"&") + "shp_xpsid=" + XPlaybackSessionId; - var r:URLRequest = new URLRequest(uri); - // seems flash not allow set this header. - // @remark disable it for it will cause security exception. - //r.requestHeaders.push(new URLRequestHeader("X-Playback-Session-Id", XPlaybackSessionId)); - - log("start download " + uri); - url.load(r); - } - - // the uuid similar to Safari, to identify this play session. - // @see https://github.com/winlinvip/srs-plus/blob/bms/trunk/src/app/srs_app_http_stream.cpp#L45 - public var XPlaybackSessionId:String = createRandomIdentifier(32); - - private function createRandomIdentifier(length:uint, radix:uint = 61):String { - var characters:Array = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', - 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', - 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', - 'z'); - var id:Array = new Array(); - radix = (radix > 61) ? 61 : radix; - while (length--) { - id.push(characters[randomIntegerWithinRange(0, radix)]); - } - return id.join(''); - } - - private function randomIntegerWithinRange(min:int, max:int):int { - return Math.floor(Math.random() * (1 + max - min) + min); - } - - // callback for hls. - public var flvHeader:ByteArray = null; - public function onSequenceHeader():void { - var s:NetStream = super; - s.appendBytesAction(NetStreamAppendBytesAction.RESET_BEGIN); - s.appendBytes(flvHeader); - log("FLV: sps/pps " + flvHeader.length + " bytes"); - - writeFlv(flvHeader); - onPlayStart(); - } - - private function onPlayStart():void { - log("dispatch NetStream.Play.Start."); - dispatchEvent(new NetStatusEvent(NetStatusEvent.NET_STATUS, false, false, { - code: "NetStream.Play.Start", - stream: user_url, - descrption: "play start" - })); - } - - private function onPlayFailed(evt:IOErrorEvent):void { - log("dispatch NetConnection.Connect.Failed."); - this.conn.dispatchEvent(new NetStatusEvent(NetStatusEvent.NET_STATUS, false, false, { - code: "NetConnection.Connect.Failed", - stream: user_url, - descrption: evt.text - })); - } - - private function onPlayRejected(evt:SecurityErrorEvent):void { - log("dispatch NetConnection.Connect.Rejected."); - this.conn.dispatchEvent(new NetStatusEvent(NetStatusEvent.NET_STATUS, false, false, { - code: "NetConnection.Connect.Rejected", - stream: user_url, - descrption: evt.text - })); - } - - private function onFlvBody(uri:String, flv:ByteArray):void { - if (!flvHeader) { - return; - } - - var s:NetStream = super; - s.appendBytes(flv); - log("FLV: ts " + uri + " parsed to flv " + flv.length + " bytes"); - - writeFlv(flv); - } - - private function writeFlv(data:ByteArray):void { - return; - - var r:URLRequest = new URLRequest("http://192.168.1.117:8088/api/v1/flv"); - r.method = URLRequestMethod.POST; - r.data = data; - - var pf:URLLoader = new URLLoader(); - pf.dataFormat = URLLoaderDataFormat.BINARY; - pf.load(r); - } - - private function log(msg:String):void { - msg = "[" + new Date() +"][srs-player] " + msg; - - trace(msg); - - if (!flash.external.ExternalInterface.available) { - return; - } - - ExternalInterface.call("console.log", msg); - } - } -} - -import flash.events.Event; -import flash.net.URLLoader; -import flash.net.URLRequest; -import flash.net.URLRequestMethod; -import flash.utils.ByteArray; - -/** - * the hls main class. - */ -class HlsCodec -{ - private var m3u8:M3u8; - - private var avc:SrsRawH264Stream; - private var h264_sps:ByteArray; - private var h264_pps:ByteArray; - - private var aac:SrsRawAacStream; - private var aac_specific_config:ByteArray; - private var width:int; - private var height:int; - - private var video_sh_tag:ByteArray; - private var audio_sh_tag:ByteArray; - - private var owner:HlsNetStream; - private var _log:ILogger = new TraceLogger("HLS"); - - public static const SRS_TS_PACKET_SIZE:int = 188; - - public function HlsCodec(o:HlsNetStream) - { - owner = o; - m3u8 = new M3u8(this); - - reset(); - } - - /** - * parse the m3u8. - * @param url, the m3u8 url, for m3u8 to generate the ts url. - * @param v, the m3u8 string. - */ - public function parse(url:String, v:String):void - { - // TODO: FIXME: reset the hls when parse. - m3u8.parse(url, v); - } - - /** - * get the total count of ts in m3u8. - */ - public function get tsCount():Number - { - return m3u8.tsCount; - } - - /** - * get the total duration in seconds of m3u8. - */ - public function get duration():Number - { - return m3u8.duration; - } - - /** - * get the sequence number, the id of first ts. - */ - public function get seq_no():Number - { - return m3u8.seq_no; - } - - /** - * whether the m3u8 contains variant m3u8. - */ - public function get variant():Boolean - { - return m3u8.variant; - } - - /** - * dumps the metadata, for example, set the width and height, - * which is decoded from sps. - */ - public function dumpMetaData(metadata:Object):void - { - if (width > 0) { - metadata.width = width; - } - if (height > 0) { - metadata.height = height; - } - } - - /** - * get the ts url by piece id, which is actually the piece index. - */ - public function getTsUrl(pieceId:Number):String - { - return m3u8.getTsUrl(pieceId); - } - - /** - * reset the HLS when parse m3u8. - */ - public function reset():void - { - avc = new SrsRawH264Stream(); - h264_sps = new ByteArray(); - h264_pps = new ByteArray(); - - aac = new SrsRawAacStream(); - aac_specific_config = new ByteArray(); - - width = 0; - height = 0; - - video_sh_tag = new ByteArray(); - audio_sh_tag = new ByteArray(); - } - - /** - * parse the piece in hls format, - * set the piece.skip if error. - * @param onParsed, a function(piece:FlvPiece, body:ByteArray):void callback. - */ - public function parseBodyAsync(piece:FlvPiece, data:ByteArray, body:ByteArray, onParsed:Function):void - { - var handler:SrsTsHanlder = new SrsTsHanlder( - avc, aac, - h264_sps, h264_pps, - aac_specific_config, - video_sh_tag, audio_sh_tag, - this, body, - _on_size_changed, _on_sequence_changed - ); - - // the context used to parse the whole ts file. - var context:SrsTsContext = new SrsTsContext(this); - - // we assumpt to parse the piece in 10 times. - // the total parse time is 10*AlgP2P.HlsAsyncParseTimeout - var ts_packets:uint = data.length / SRS_TS_PACKET_SIZE; - var each_parse:uint = ts_packets / 10; - var nb_parsed:uint = 0; - var aysncParse:Function = function():void { - try { - // do the parse. - doParseBody(piece, data, body, handler, context, each_parse); - - // check whether parsed. - nb_parsed += each_parse; - - if (nb_parsed < ts_packets) { - flash.utils.setTimeout(aysncParse, owner.ts_parse_async_interval); - return; - } - - // flush the messages in queue. - handler.flush_message_queue(body); - - __report(body); - _log.info("hls async parsed to flv, piece={0}, hls={1}B, flv={2}B", piece.pieceId, data.length, body.length); - } catch (e:Error) { - piece.skip = true; - _log.error("hls async parse piece={0}, exception={1}, stack={2}", - piece.pieceId, e.message, e.getStackTrace()); - } - - onParsed(piece, body); - }; - - aysncParse(); - } - - /** - * parse the piece in hls format, - * set the piece.skip if error. - */ - public function parseBody(piece:FlvPiece, data:ByteArray, body:ByteArray):void - { - try { - var handler:SrsTsHanlder = new SrsTsHanlder( - avc, aac, - h264_sps, h264_pps, - aac_specific_config, - video_sh_tag, audio_sh_tag, - this, body, - _on_size_changed, _on_sequence_changed - ); - - // the context used to parse the whole ts file. - var context:SrsTsContext = new SrsTsContext(this); - - // do the parse. - doParseBody(piece, data, body, handler, context, -1); - - // flush the messages in queue. - handler.flush_message_queue(body); - - __report(body); - _log.info("hls sync parsed to flv, piece={0}, hls={1}B, flv={2}B", piece.pieceId, data.length, body.length); - } catch (e:Error) { - piece.skip = true; - _log.error("hls sync parse piece={0}, exception={1}, stack={2}", - piece.pieceId, e.message, e.getStackTrace()); - } - } - - private function _on_size_changed(w:int, h:int):void - { - width = w; - height = h; - } - - private function _on_sequence_changed( - pavc:SrsRawH264Stream, paac:SrsRawAacStream, - ph264_sps:ByteArray, ph264_pps:ByteArray, - paac_specific_config:ByteArray, - pvideo_sh_tag:ByteArray, paudio_sh_tag:ByteArray, - sh:ByteArray):void - { - // when sequence header not changed, ignore. - if (SrsUtils.array_equals(h264_sps, ph264_sps)) { - if (SrsUtils.array_equals(h264_pps, ph264_pps)) { - if (SrsUtils.array_equals(aac_specific_config, paac_specific_config)) { - return; - } - } - } - - avc = pavc; - h264_sps = ph264_sps; - h264_pps = ph264_pps; - - aac = paac; - aac_specific_config = paac_specific_config; - - video_sh_tag = pvideo_sh_tag; - audio_sh_tag = paudio_sh_tag; - - _log.info("hls: got sequence header, ash={0}B, bsh={1}B", audio_sh_tag.length, video_sh_tag.length); - owner.flvHeader = sh; - owner.onSequenceHeader(); - - __report(sh); - } - - /** - * do the parse. - * @maxTsPackets the max ts packets to parse, stop when exceed this ts packet. - * -1 to parse all packets. - */ - private function doParseBody( - piece:FlvPiece, data:ByteArray, body:ByteArray, - handler:SrsTsHanlder, context:SrsTsContext, maxTsPackets:int):void - { - for (var i:int = 0; (maxTsPackets == -1 || i < maxTsPackets) && data.bytesAvailable > 0; i++) { - var tsBytes:ByteArray = new ByteArray(); - data.readBytes(tsBytes, 0, HlsCodec.SRS_TS_PACKET_SIZE); - context.decode(tsBytes, handler); - } - } - - private function __report(flv:ByteArray):void - { - // report only for debug. - return; - - var url:URLRequest = new URLRequest("http://192.168.10.108:1980/api/v3/file"); - url.data = flv; - url.method = URLRequestMethod.POST; - - var loader:URLLoader = new URLLoader(); - loader.addEventListener(Event.COMPLETE, function(e:Event):void { - loader.close(); - }); - loader.load(url); - } -} - -import flash.utils.Dictionary; - -class Dict -{ - private var _dict:Dictionary; - private var _size:uint; - - public function Dict() - { - clear(); - } - - /** - * get the underlayer dict. - * @remark for core-ng. - */ - public function get dict():Dictionary - { - return _dict; - } - - public function has(key:Object):Boolean - { - return (key in _dict); - } - - public function get(key:Object):Object - { - return ((key in _dict) ? _dict[key] : null); - } - - public function set(key:Object, object:Object):void - { - if (!(key in _dict)) - { - _size++; - } - _dict[key] = object; - } - - public function remove(key:Object):Object - { - var object:Object; - if (key in _dict) - { - object = _dict[key]; - delete _dict[key]; - _size--; - } - return (object); - } - - public function keys():Array - { - var array:Array = new Array(_size); - var index:int; - for (var key:Object in _dict) - { - var _local6:int = index++; - array[_local6] = key; - } - return (array); - } - - public function values():Array - { - var array:Array = new Array(_size); - var index:int; - for each (var value:Object in _dict) - { - var _local6:int = index++; - array[_local6] = value; - }; - return (array); - } - - public function clear():void - { - _dict = new Dictionary(); - _size = 0; - } - - public function toArray():Array - { - var array:Array = new Array(_size * 2); - var index:int; - for (var key:Object in _dict) - { - var _local6:int = index++; - array[_local6] = key; - var _local7:int = index++; - array[_local7] = _dict[key]; - }; - return (array); - } - - public function toObject():Object - { - return (toArray()); - } - - public function fromObject(object:Object):void - { - clear(); - var index:uint; - while (index < (object as Array).length) { - set((object as Array)[index], (object as Array)[(index + 1)]); - index += 2; - }; - } - - public function get size():uint - { - return (_size); - } - -} - -import flash.utils.ByteArray; - -/** - * a piece of flv, fetch from cdn or p2p. - */ -class FlvPiece -{ - private var _pieceId:Number; - protected var _flv:ByteArray; - /** - * the private object for the channel, - * for example, the cdn channel will set to CdnEdge object. - */ - private var _privateObject:Object; - /** - * when encoder error, this piece cannot be generated, - * and it should be skip. default to false. - */ - private var _skip:Boolean; - - public function FlvPiece(pieceId:Number) - { - _pieceId = pieceId; - _flv = null; - _skip = false; - } - - /** - * when piece is fetch ok. - */ - public function onPieceDone(flv:ByteArray):void - { - // save body. - _flv = flv; - } - - /** - * when piece is fetch error. - */ - public function onPieceError():void - { - } - - /** - * when piece is empty. - */ - public function onPieceEmpty():void - { - } - - /** - * destroy the object, set reference to null. - */ - public function destroy():void - { - _privateObject = null; - _flv = null; - } - - public function get privateObject():Object - { - return _privateObject; - } - - public function set privateObject(v:Object):void - { - _privateObject = v; - } - - public function get skip():Boolean - { - return _skip; - } - - public function set skip(v:Boolean):void - { - _skip = v; - } - - public function get pieceId():Number - { - return _pieceId; - } - - public function get flv():ByteArray - { - return _flv; - } - - public function get completed():Boolean - { - return _flv != null; - } -} - -interface ILogger -{ - function debug0(message:String, ... rest):void; - function debug(message:String, ... rest):void; - function info(message:String, ... rest):void; - function warn(message:String, ... rest):void; - function error(message:String, ... rest):void; - function fatal(message:String, ... rest):void; -} - -import flash.globalization.DateTimeFormatter; -import flash.external.ExternalInterface; - -class TraceLogger implements ILogger -{ - private var _category:String; - - public function get category():String - { - return _category; - } - public function TraceLogger(category:String) - { - _category = category; - } - public function debug0(message:String, ...rest):void - { - } - - public function debug(message:String, ...rest):void - { - } - - public function info(message:String, ...rest):void - { - logMessage(LEVEL_INFO, message, rest); - } - - public function warn(message:String, ...rest):void - { - logMessage(LEVEL_WARN, message, rest); - } - - public function error(message:String, ...rest):void - { - logMessage(LEVEL_ERROR, message, rest); - } - - public function fatal(message:String, ...rest):void - { - logMessage(LEVEL_FATAL, message, rest); - } - protected function logMessage(level:String, message:String, params:Array):void - { - var msg:String = ""; - - // add datetime - var date:Date = new Date(); - var dtf:DateTimeFormatter = new DateTimeFormatter("UTC"); - dtf.setDateTimePattern("yyyy-MM-dd HH:mm:ss"); - - // TODO: FIXME: the SSS format not run, use date.milliseconds instead. - msg += '[' + dtf.format(date) + "." + date.milliseconds + ']'; - msg += " [" + level + "] "; - - // add category and params - msg += "[" + category + "] " + applyParams(message, params); - - // trace the message - trace(msg); - - if (!flash.external.ExternalInterface.available) { - return; - } - - ExternalInterface.call("console.log", msg); - } - private function leadingZeros(x:Number):String - { - if (x < 10) { - return "00" + x.toString(); - } - - if (x < 100) { - return "0" + x.toString(); - } - - return x.toString(); - } - private function applyParams(message:String, params:Array):String - { - var result:String = message; - - var numParams:int = params.length; - - for (var i:int = 0; i < numParams; i++) { - result = result.replace(new RegExp("\\{" + i + "\\}", "g"), params[i]); - } - return result; - } - - private static const LEVEL_DEBUG:String = "DEBUG"; - private static const LEVEL_WARN:String = "WARN"; - private static const LEVEL_INFO:String = "INFO"; - private static const LEVEL_ERROR:String = "ERROR"; - private static const LEVEL_FATAL:String = "FATAL"; -} - -import flash.utils.ByteArray; - -class SrsTsHanlder implements ISrsTsHandler -{ - private var avc:SrsRawH264Stream; - private var h264_sps:ByteArray; - private var h264_pps:ByteArray; - private var h264_sps_changed:Boolean; - private var h264_pps_changed:Boolean; - - private var aac:SrsRawAacStream; - private var aac_specific_config:ByteArray; - private var width:int; - private var height:int; - - private var video_sh_tag:ByteArray; - private var audio_sh_tag:ByteArray; - - private var queue:Array; - - // hls data. - private var _hls:HlsCodec; - private var _body:ByteArray; - private var _on_size_changed:Function; - private var _on_sequence_changed:Function; - - private var _log:ILogger = new TraceLogger("HLS"); - - public function SrsTsHanlder( - pavc:SrsRawH264Stream, paac:SrsRawAacStream, - ph264_sps:ByteArray, ph264_pps:ByteArray, - paac_specific_config:ByteArray, - pvideo_sh_tag:ByteArray, paudio_sh_tag:ByteArray, - hls:HlsCodec, body:ByteArray, oszc:Function, oshc:Function) - { - _hls = hls; - _body = body; - _on_size_changed = oszc; - _on_sequence_changed = oshc; - - avc = pavc; - h264_sps = ph264_sps; - h264_pps = ph264_pps; - - aac = paac; - aac_specific_config = paac_specific_config; - - video_sh_tag = pvideo_sh_tag; - audio_sh_tag = paudio_sh_tag; - - queue = new Array(); - width = 0; - height = 0; - h264_sps_changed = false; - h264_pps_changed = false; - } - - public function on_ts_message(msg:SrsTsMessage):void - { - do_on_ts_message(msg, _body); - } - - private function do_on_ts_message(msg:SrsTsMessage, body:ByteArray):void - { - // @see SrsMpegtsOverUdp::on_ts_message - if (false) { - _log.info("got ts {4} message, dts={0}, pts={1}, size={2}/{3}", - msg.dts, msg.pts, msg.PES_packet_length, msg.payload.length, - (msg.channel.apply.equals(SrsTsPidApply.Video)? "Video":"Audio")); - } else { - _log.debug("got ts {4} message, dts={0}, pts={1}, size={2}/{3}", - msg.dts, msg.pts, msg.PES_packet_length, msg.payload.length, - (msg.channel.apply.equals(SrsTsPidApply.Video)? "Video":"Audio")); - } - - // when not audio/video, or not adts/annexb format, donot support. - if (msg.stream_number() != 0) { - throw new Error("mpegts: unsupported stream format, sid=" + msg.stream_number()); - } - - // check supported codec - if (msg.channel.stream != SrsTsStream.VideoH264 && msg.channel.stream != SrsTsStream.AudioAAC) { - throw new Error("mpegts: unsupported stream codec=" + msg.channel.stream.toString()); - } - - // we must use queue to cache the msg, then parse it if possible. - queue.push(msg); - parse_message_queue(body); - } - - private function parse_message_queue(body:ByteArray):void - { - if (queue.length == 0) { - return; - } - - var first_ts_msg:SrsTsMessage = queue[0] as SrsTsMessage; - var context:SrsTsContext = first_ts_msg.channel.context; - var cpa:Boolean = context.is_pure_audio(); - - var nb_videos:uint = 0; - if (!cpa) { - for (var i:int = 0; i < queue.length; i++) { - var msg:SrsTsMessage = queue[i] as SrsTsMessage; - - // publish audio or video. - if (msg.channel.stream == SrsTsStream.VideoH264) { - nb_videos++; - } - } - - // always wait 2+ videos, to left one video in the queue. - // TODO: FIXME: support pure audio hls. - if (nb_videos <= 1) { - return; - } - } - - // we must sort the adio and videos, for they maybe not monotonically increase. - queue.sort(function(a:SrsTsMessage, b:SrsTsMessage):int{ - return a.dts - b.dts; - }); - - // parse messages util the last video. - while ((cpa && queue.length > 1) || nb_videos > 1) { - if (queue.length == 0) { - throw new Error("assert queue not empty."); - } - - msg = queue[0] as SrsTsMessage; - if (msg.channel.stream == SrsTsStream.VideoH264) { - nb_videos--; - } - queue.splice(0, 1); - - // publish audio or video. - if (msg.channel.stream == SrsTsStream.VideoH264) { - on_ts_video(msg, body); - } - if (msg.channel.stream == SrsTsStream.AudioAAC) { - on_ts_audio(msg, body); - } - } - } - - public function flush_message_queue(body:ByteArray):void - { - for (var i:int = 0; i < queue.length; i++) { - var msg:SrsTsMessage = queue[i] as SrsTsMessage; - - // publish audio or video. - if (msg.channel.stream == SrsTsStream.VideoH264) { - on_ts_video(msg, body); - } - if (msg.channel.stream == SrsTsStream.AudioAAC) { - on_ts_audio(msg, body); - } - } - - // clear queue. - queue = new Array(); - } - - private function on_ts_video(msg:SrsTsMessage, body:ByteArray):void - { - // ts tbn to flv tbn. - var dts:uint = (uint)(msg.dts / 90); - var pts:uint = (uint)(msg.pts / 90); - - var ibps:ByteArray = new ByteArray(); - var frame_type:uint = SrsConsts.SrsCodecVideoAVCFrameInterFrame; - - // each frame must prefixed by annexb format. - // first check the msg.payload outside the while cycle, to avoid throw error inside. - // if msg.payload not startwith annexb, just return. - var annexb:Object = SrsUtils.srs_avc_startswith_annexb(msg.payload); - if (!annexb.ok) { - _log.warn("msg.payload not startwith annexb, drop size={0}B, dts={1}", msg.payload.length, dts); - return; - } - - // group each NALU frame to a RTMP/flv/ts message - while (msg.payload.bytesAvailable) { - var frame:ByteArray = avc.annexb_demux(msg.payload); - - // 5bits, 7.3.1 NAL unit syntax, - // H.264-AVC-ISO_IEC_14496-10.pdf, page 44. - // 7: SPS, 8: PPS, 5: I Frame, 1: P Frame - var nal_unit_type:uint = (uint)(frame[0] & 0x1f); - - // for IDR frame, the frame is keyframe. - if (nal_unit_type == SrsConsts.SrsAvcNaluTypeIDR) { - frame_type = SrsConsts.SrsCodecVideoAVCFrameKeyFrame; - } - - // ignore the nalu type aud(9) - if (nal_unit_type == SrsConsts.SrsAvcNaluTypeAccessUnitDelimiter) { - var aud_nalu:String = ""; - for (var i:int = 0; i < frame.length; i++) { - aud_nalu += " 0x" + int(frame[i]).toString(16); - } - _log.debug("hls, aud nalu: {0}", aud_nalu); - continue; - } - - // for sps - if (avc.is_sps(frame)) { - var sps:ByteArray = avc.sps_demux(frame); - - if (SrsUtils.array_equals(h264_sps, sps)) { - continue; - } - h264_sps = sps; - h264_sps_changed = true; - - // demux the sps, get the width x height. - avc_demux_sps(sps); - - if (false) { - _log.info("hls: got sps, size={0}B", sps.length); - } else { - _log.debug("hls: got sps, size={0}B", sps.length); - } - continue; - } - - // for pps - if (avc.is_pps(frame)) { - var pps:ByteArray = avc.pps_demux(frame); - - if (SrsUtils.array_equals(h264_pps, pps)) { - continue; - } - h264_pps = pps; - h264_pps_changed = true; - - if (false) { - _log.info("hls: got pps, size={0}B", pps.length); - } else { - _log.debug("hls: got pps, size={0}B", pps.length); - } - continue; - } - - // ibp frame. - //info("mpegts: demux avc ibp frame size=%d, dts=%d", ibpframe_size, dts); - var ibp:ByteArray = avc.mux_ipb_frame(frame); - ibps.writeBytes(ibp); - } - - write_h264_sps_pps(msg.channel.context, dts, pts); - write_h264_ipb_frame(ibps, frame_type, dts, pts, body); - } - - private function on_ts_audio(msg:SrsTsMessage, piece:ByteArray):void - { - // ts tbn to flv tbn. - var dts:uint = msg.dts / 90; - - // got the next message to calc the delta duration for each audio. - var duration:uint = 0; - if (queue.length > 0) { - var nm:SrsTsMessage = queue[0] as SrsTsMessage; - duration = (uint)(Math.max(0, nm.dts - msg.dts) / 90); - } - var min_dts:uint = dts; - var max_dts:uint = min_dts + duration; - - // send each frame. - while (msg.payload.bytesAvailable) { - var ret:Object = aac.adts_demux(msg.payload); - var frame:ByteArray = ret.frame; - var codec:SrsRawAacStreamCodec = ret.codec; - - // ignore invalid frame, - // * atleast 1bytes for aac to decode the data. - if (!frame.bytesAvailable) { - continue; - } - //info("mpegts: demux aac frame size=%d, dts=%d", frame_size, dts); - - // generate sh. - if (!aac_specific_config.length) { - aac_specific_config = aac.mux_sequence_header(codec); - codec.aac_packet_type = 0; - if (false) { - _log.info("hls: got audio specific config, size={0}B", aac_specific_config.length); - } else { - _log.debug("hls: got audio specific config, size={0}B", aac_specific_config.length); - } - - var tag_body:ByteArray = aac.mux_aac2flv(aac_specific_config, codec, dts); - audio_sh_tag = mux_flv_packet(SrsConsts.SrsCodecFlvTagAudio, dts, tag_body); - on_sequence_header(msg.channel.context); - } - - // audio raw data. - codec.aac_packet_type = 1; - write_audio_raw_frame(frame, codec, dts, piece); - - // calc the delta of dts, when previous frame output. - var delta:uint = duration / (msg.payload.length / frame.length); - dts = (uint)(Math.min(max_dts, dts + delta)); - - if (msg.payload.bytesAvailable) { - _log.debug("Audio [{0}, {1}], the A2+ is {2}", min_dts, max_dts, dts); - } - } - } - - private function write_audio_raw_frame(frame:ByteArray, codec:SrsRawAacStreamCodec, dts:uint, piece:ByteArray):void - { - var tag_body:ByteArray = aac.mux_aac2flv(frame, codec, dts); - var tag:ByteArray = mux_flv_packet(SrsConsts.SrsCodecFlvTagAudio, dts, tag_body); - - // append flv packet to piece. - piece.writeBytes(tag); - } - - private function avc_demux_sps(sps:ByteArray):void - { - sps.position = 0; - - if (false) { - var str:String = ""; - for (var i:int = 0; i < sps.length; i++) { - str += " 0x" + int(sps[i]).toString(16); - } - } - - var nalu_type:uint = sps.readUnsignedByte(); - if ((nalu_type & 0x1f) != SrsConsts.SrsAvcNaluTypeSPS) { - _log.warn("avc: sps nalu type invalid."); - return; - } - - var rbsp:ByteArray = new ByteArray(); - while (sps.bytesAvailable) { - rbsp.writeByte(sps.readByte()); - - // XX 00 00 03 XX, the 03 byte should be drop. - var nb_rbsp:uint = rbsp.length; - if (nb_rbsp > 2) { - var p2:uint = rbsp[nb_rbsp - 3]; - var p1:uint = rbsp[nb_rbsp - 2]; - var p0:uint = rbsp[nb_rbsp - 1]; - if (p2 == 0 && p1 == 0 && p0 == 3) { - rbsp.position = rbsp.length - 1; - - // read 1 byte more. - if (!sps.bytesAvailable) { - break; - } - rbsp.writeByte(sps.readByte()); - } - } - } - - try { - avc_demux_sps_rbsp(rbsp); - } catch (e:Error) { - _log.warn(e.message); - } - } - private function avc_demux_sps_rbsp(rbsp:ByteArray):void - { - rbsp.position = 0; - - // for SPS, 7.3.2.1.1 Sequence parameter set data syntax - // H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 62. - if (rbsp.bytesAvailable < 3) { - _log.warn("avc: sps shall atleast 3bytes"); - return; - } - - var profile_idc:uint = rbsp.readUnsignedByte(); - var flags:uint = rbsp.readUnsignedByte(); - var level_idc:uint = rbsp.readUnsignedByte(); - - var bs:SrsBitStream = new SrsBitStream(rbsp); - var seq_parameter_set_id:uint = SrsUtils.srs_avc_nalu_read_uev(bs); - _log.debug("sps parse profile={0}, level={1}, sps_id={2}", profile_idc, level_idc, seq_parameter_set_id); - - if (profile_idc == 100 || profile_idc == 110 || profile_idc == 122 || profile_idc == 244 - || profile_idc == 44 || profile_idc == 83 || profile_idc == 86 || profile_idc == 118 - || profile_idc == 128 - ) { - var chroma_format_idc:int = SrsUtils.srs_avc_nalu_read_uev(bs); - if (chroma_format_idc == 3) { - var separate_colour_plane_flag:int = SrsUtils.srs_avc_nalu_read_bit(bs); - } - var bit_depth_luma_minus8:int = SrsUtils.srs_avc_nalu_read_uev(bs); - var bit_depth_chroma_minus8:int = SrsUtils.srs_avc_nalu_read_uev(bs); - var qpprime_y_zero_transform_bypass_flag:int = SrsUtils.srs_avc_nalu_read_bit(bs); - var seq_scaling_matrix_present_flag:int = SrsUtils.srs_avc_nalu_read_bit(bs); - if (seq_scaling_matrix_present_flag) { - throw new Error("sps seq_scaling_matrix_present_flag not zero."); - } - _log.debug("sps cfi={0}, bdlm={1}, bdcm={2}, qyztb={3}, ssmpf={4}", - chroma_format_idc, bit_depth_luma_minus8, bit_depth_chroma_minus8, - qpprime_y_zero_transform_bypass_flag, seq_scaling_matrix_present_flag); - } - - var log2_max_frame_num_minus4:int = SrsUtils.srs_avc_nalu_read_uev(bs); - var pic_order_cnt_type:int = SrsUtils.srs_avc_nalu_read_uev(bs); - if (pic_order_cnt_type == 0) { - var log2_max_pic_order_cnt_lsb_minus4:int = SrsUtils.srs_avc_nalu_read_uev(bs); - _log.debug("sps lmpoclm={0}", log2_max_pic_order_cnt_lsb_minus4); - } else if (pic_order_cnt_type == 1) { - var delta_pic_order_always_zero_flag:int = SrsUtils.srs_avc_nalu_read_bit(bs); - var offset_for_non_ref_pic:int = SrsUtils.srs_avc_nalu_read_uev(bs); - var offset_for_top_to_bottom_field:int = SrsUtils.srs_avc_nalu_read_uev(bs); - var num_ref_frames_in_pic_order_cnt_cycle:int = SrsUtils.srs_avc_nalu_read_uev(bs); - _log.debug("sps dpoazf={0}, ofnrp={1}, ofttbf={2}, nrfipocc={3}", - delta_pic_order_always_zero_flag, offset_for_non_ref_pic, offset_for_top_to_bottom_field, - num_ref_frames_in_pic_order_cnt_cycle); - } - var max_num_ref_frames:int = SrsUtils.srs_avc_nalu_read_uev(bs); - var gaps_in_frame_num_value_allowed_flag:int = SrsUtils.srs_avc_nalu_read_bit(bs); - var pic_width_in_mbs_minus1:int = SrsUtils.srs_avc_nalu_read_uev(bs); - var pic_height_in_map_units_minus1:int = SrsUtils.srs_avc_nalu_read_uev(bs); - - width = (int)(pic_width_in_mbs_minus1 + 1) * 16; - height = (int)(pic_height_in_map_units_minus1 + 1) * 16; - _log.info("sps parse profile={0}, level={1}, size={2}x{3}", profile_idc, level_idc, width, height); - - _on_size_changed(width, height); - } - - private function write_h264_sps_pps(context:SrsTsContext, dts:uint, pts:uint):void - { - if (!h264_sps_changed && !h264_pps_changed) { - return; - } - - // when not got sps/pps, wait. - if (h264_pps.length == 0 || h264_sps.length == 0) { - return; - } - - // h264 raw to h264 packet. - var sh:ByteArray = avc.mux_sequence_header(h264_sps, h264_pps, dts, pts); - - // h264 packet to flv packet. - var frame_type:uint = SrsConsts.SrsCodecVideoAVCFrameKeyFrame; - var avc_packet_type:uint = SrsConsts.SrsCodecVideoAVCTypeSequenceHeader; - var tag_body:ByteArray = avc.mux_avc2flv(sh, frame_type, avc_packet_type, dts, pts); - - // the timestamp in rtmp message header is dts. - video_sh_tag = mux_flv_packet(SrsConsts.SrsCodecFlvTagVideo, dts, tag_body); - on_sequence_header(context); - } - - private function write_h264_ipb_frame(ibps:ByteArray, frame_type:uint, dts:uint, pts:uint, piece:ByteArray):void - { - // when sps or pps not sent, ignore the packet. - // @see https://github.com/ossrs/srs/issues/203 - if (video_sh_tag.length == 0) { - return; - } - - var avc_packet_type:uint = SrsConsts.SrsCodecVideoAVCTypeNALU; - var tag_body:ByteArray = avc.mux_avc2flv(ibps, frame_type, avc_packet_type, dts, pts); - - // the timestamp in rtmp message header is dts. - var timestamp:uint = dts; - var tag:ByteArray = mux_flv_packet(SrsConsts.SrsCodecFlvTagVideo, timestamp, tag_body); - - // append flv packet to piece. - piece.writeBytes(tag); - } - - private function mux_flv_packet(type:uint, timestamp:uint, flv:ByteArray):ByteArray - { - // E.4.1 FLV Tag - var packet:ByteArray = new ByteArray(); - - // Reserved UB [2] - // Filter UB [1] - // TagType UB [5] - packet.writeByte(type & 0x1f); - - // DataSize UI24 - var size:uint = flv.length; - packet.writeByte(size >> 16); - packet.writeByte(size >> 8); - packet.writeByte(size); - - // Timestamp UI24 - var dts:uint = timestamp; - packet.writeByte(dts >> 16); - packet.writeByte(dts >> 8); - packet.writeByte(dts); - - // TimestampExtended UI8 - packet.writeByte(dts >> 24); - - // StreamID, UI24, Always 0. - packet.writeByte(0x00); - packet.writeByte(0x00); - packet.writeByte(0x00); - - // tag body. - packet.writeBytes(flv); - - // PreviousTagSizeN, UI32, Size of last tag, including its header, in bytes. - size = packet.length; - packet.writeUnsignedInt(size); - - if (false) { - _log.info("FLV: mux flv type={0}, time={1}, size={3}", type, timestamp, dts, packet.length); - } else { - _log.debug("mux flv type={0}, time={1}, size={3}", type, timestamp, dts, packet.length); - } - - return packet; - } - - private function on_sequence_header(context:SrsTsContext):void - { - if (!audio_sh_tag.length) { - return; - } - if (!context.is_pure_audio() && !video_sh_tag.length) { - return; - } - - var sh:ByteArray = new ByteArray(); - - // @remark HSS without flv header. - // 9bytes header and 4bytes first previous-tag-size - // Signatures "FLV" - sh.writeByte(0x46); // 'F' - sh.writeByte(0x4c); // 'L' - sh.writeByte(0x56); // 'V' - // File version (for example, 0x01 for FLV version 1) - sh.writeByte(0x01); - // 4, audio; 1, video; 5 audio+video. - if (context.is_pure_audio()) { - sh.writeByte(0x04); - } else { - sh.writeByte(0x05); - } - // DataOffset UI32 The length of this header in bytes - sh.writeUnsignedInt(0x00000009); - // previous tag size. - sh.writeUnsignedInt(0x00000000); - - // append video and audio sequence header. - if (!context.is_pure_audio()) { - sh.writeBytes(video_sh_tag); - } - sh.writeBytes(audio_sh_tag); - - // reset the positions. - h264_sps.position = 0; - h264_pps.position = 0; - aac_specific_config.position = 0; - video_sh_tag.position = 0; - audio_sh_tag.position = 0; - - // notice the HLS to change sh if should to. - _on_sequence_changed( - avc, aac, - h264_sps, h264_pps, - aac_specific_config, - video_sh_tag, audio_sh_tag, - sh - ); - } -} - -class SrsConsts -{ - // E.4.3.1 VIDEODATA - // Frame Type UB [4] - // Type of video frame. The following values are defined: - // 1 = key frame (for AVC, a seekable frame) - // 2 = inter frame (for AVC, a non-seekable frame) - // 3 = disposable inter frame (H.263 only) - // 4 = generated key frame (reserved for server use only) - // 5 = video info/command frame - public static const SrsCodecVideoAVCFrameReserved:uint = 0; - public static const SrsCodecVideoAVCFrameReserved1:uint = 6; - - public static const SrsCodecVideoAVCFrameKeyFrame:uint = 1; - public static const SrsCodecVideoAVCFrameInterFrame:uint = 2; - public static const SrsCodecVideoAVCFrameDisposableInterFrame:uint = 3; - public static const SrsCodecVideoAVCFrameGeneratedKeyFrame:uint = 4; - public static const SrsCodecVideoAVCFrameVideoInfoFrame:uint = 5; - - // AACPacketType IF SoundFormat == 10 UI8 - // The following values are defined: - // 0 = AAC sequence header - // 1 = AAC raw - public static const SrsCodecAudioTypeReserved:uint = 2; - - public static const SrsCodecAudioTypeSequenceHeader:uint = 0; - public static const SrsCodecAudioTypeRawData:uint = 1; - - // AVCPacketType IF CodecID == 7 UI8 - // The following values are defined: - // 0 = AVC sequence header - // 1 = AVC NALU - // 2 = AVC end of sequence (lower level NALU sequence ender is - // not required or supported) - public static const SrsCodecVideoAVCTypeReserved:uint = 3; - - public static const SrsCodecVideoAVCTypeSequenceHeader:uint = 0; - public static const SrsCodecVideoAVCTypeNALU:uint = 1; - public static const SrsCodecVideoAVCTypeSequenceHeaderEOF:uint = 2; - - // E.4.3.1 VIDEODATA - // CodecID UB [4] - // Codec Identifier. The following values are defined: - // 2 = Sorenson H.263 - // 3 = Screen video - // 4 = On2 VP6 - // 5 = On2 VP6 with alpha channel - // 6 = Screen video version 2 - // 7 = AVC - public static const SrsCodecVideoReserved:uint = 0; - public static const SrsCodecVideoReserved1:uint = 1; - public static const SrsCodecVideoReserved2:uint = 9; - - // for user to disable video, for example, use pure audio hls. - public static const SrsCodecVideoDisabled:uint = 8; - - public static const SrsCodecVideoSorensonH263:uint = 2; - public static const SrsCodecVideoScreenVideo:uint = 3; - public static const SrsCodecVideoOn2VP6:uint = 4; - public static const SrsCodecVideoOn2VP6WithAlphaChannel:uint = 5; - public static const SrsCodecVideoScreenVideoVersion2:uint = 6; - public static const SrsCodecVideoAVC:uint = 7; - - // SoundFormat UB [4] - // Format of SoundData. The following values are defined: - // 0 = Linear PCM, platform endian - // 1 = ADPCM - // 2 = MP3 - // 3 = Linear PCM, little endian - // 4 = Nellymoser 16 kHz mono - // 5 = Nellymoser 8 kHz mono - // 6 = Nellymoser - // 7 = G.711 A-law logarithmic PCM - // 8 = G.711 mu-law logarithmic PCM - // 9 = reserved - // 10 = AAC - // 11 = Speex - // 14 = MP3 8 kHz - // 15 = Device-specific sound - // Formats 7, 8, 14, and 15 are reserved. - // AAC is supported in Flash Player 9,0,115,0 and higher. - // Speex is supported in Flash Player 10 and higher. - public static const SrsCodecAudioReserved1:uint = 16; - - public static const SrsCodecAudioLinearPCMPlatformEndian:uint = 0; - public static const SrsCodecAudioADPCM:uint = 1; - public static const SrsCodecAudioMP3:uint = 2; - public static const SrsCodecAudioLinearPCMLittleEndian:uint = 3; - public static const SrsCodecAudioNellymoser16kHzMono:uint = 4; - public static const SrsCodecAudioNellymoser8kHzMono:uint = 5; - public static const SrsCodecAudioNellymoser:uint = 6; - public static const SrsCodecAudioReservedG711AlawLogarithmicPCM:uint = 7; - public static const SrsCodecAudioReservedG711MuLawLogarithmicPCM:uint = 8; - public static const SrsCodecAudioReserved:uint = 9; - public static const SrsCodecAudioAAC:uint = 10; - public static const SrsCodecAudioSpeex:uint = 11; - public static const SrsCodecAudioReservedMP3_8kHz:uint = 14; - public static const SrsCodecAudioReservedDeviceSpecificSound:uint = 15; - - /** - * the FLV/RTMP supported audio sample rate. - * Sampling rate. The following values are defined: - * 0 = 5.5 kHz = 5512 Hz - * 1 = 11 kHz = 11025 Hz - * 2 = 22 kHz = 22050 Hz - * 3 = 44 kHz = 44100 Hz - */ - public static const SrsCodecAudioSampleRateReserved:uint = 4; - - public static const SrsCodecAudioSampleRate5512:uint = 0; - public static const SrsCodecAudioSampleRate11025:uint = 1; - public static const SrsCodecAudioSampleRate22050:uint = 2; - public static const SrsCodecAudioSampleRate44100:uint = 3; - - /** - * E.4.1 FLV Tag, page 75 - */ - public static const SrsCodecFlvTagReserved:uint = 0; - - // 8 = audio - public static const SrsCodecFlvTagAudio:uint = 8; - // 9 = video - public static const SrsCodecFlvTagVideo:uint = 9; - // 18 = script data - public static const SrsCodecFlvTagScript:uint = 18; - - /** - * Table 7-1 – NAL unit type codes, syntax element categories, and NAL unit type classes - * H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 83. - */ - // Coded slice of a non-IDR picture slice_layer_without_partitioning_rbsp( ) - public static const SrsAvcNaluTypeNonIDR:uint = 1; - // Coded slice data partition A slice_data_partition_a_layer_rbsp( ) - public static const SrsAvcNaluTypeDataPartitionA:uint = 2; - // Coded slice data partition B slice_data_partition_b_layer_rbsp( ) - public static const SrsAvcNaluTypeDataPartitionB:uint = 3; - // Coded slice data partition C slice_data_partition_c_layer_rbsp( ) - public static const SrsAvcNaluTypeDataPartitionC:uint = 4; - // Coded slice of an IDR picture slice_layer_without_partitioning_rbsp( ) - public static const SrsAvcNaluTypeIDR:uint = 5; - // Supplemental enhancement information (SEI) sei_rbsp( ) - public static const SrsAvcNaluTypeSEI:uint = 6; - // Sequence parameter set seq_parameter_set_rbsp( ) - public static const SrsAvcNaluTypeSPS:uint = 7; - // Picture parameter set pic_parameter_set_rbsp( ) - public static const SrsAvcNaluTypePPS:uint = 8; - // Access unit delimiter access_unit_delimiter_rbsp( ) - public static const SrsAvcNaluTypeAccessUnitDelimiter:uint = 9; - // End of sequence end_of_seq_rbsp( ) - public static const SrsAvcNaluTypeEOSequence:uint = 10; - // End of stream end_of_stream_rbsp( ) - public static const SrsAvcNaluTypeEOStream:uint = 11; - // Filler data filler_data_rbsp( ) - public static const SrsAvcNaluTypeFilterData:uint = 12; - // Sequence parameter set extension seq_parameter_set_extension_rbsp( ) - public static const SrsAvcNaluTypeSPSExt:uint = 13; - // Prefix NAL unit prefix_nal_unit_rbsp( ) - public static const SrsAvcNaluTypePrefixNALU:uint = 14; - // Subset sequence parameter set subset_seq_parameter_set_rbsp( ) - public static const SrsAvcNaluTypeSubsetSPS:uint = 15; - // Coded slice of an auxiliary coded picture without partitioning slice_layer_without_partitioning_rbsp( ) - public static const SrsAvcNaluTypeLayerWithoutPartition:uint = 19; - // Coded slice extension slice_layer_extension_rbsp( ) - public static const SrsAvcNaluTypeCodedSliceExt:uint = 20; -} - -class SrsUtils -{ - /* - * MPEG2 transport stream (aka DVB) mux - * Copyright (c) 2003 Fabrice Bellard. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - private static const crc_table:Array = [ - 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, - 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, - 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7, - 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, - 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, - 0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, - 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef, - 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, - 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, - 0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, - 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, - 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, - 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, - 0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, - 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, - 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, - 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, - 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, - 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050, - 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, - 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, - 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, - 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1, - 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, - 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, - 0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, - 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9, - 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, - 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, - 0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, - 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, - 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, - 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, - 0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, - 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, - 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, - 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, - 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, - 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676, - 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, - 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, - 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, - 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 - ]; - - // @see http://www.stmc.edu.hk/~vincent/ffmpeg_0.4.9-pre1/libavformat/mpegtsenc.c - private static function mpegts_crc32(bytes:ByteArray):uint - { - var crc:uint = 0xffffffff; - - for (var i:int = 0; i < bytes.length; i++) { - crc = (crc << 8) ^ crc_table[((crc >> 24) ^ bytes[i]) & 0xff]; - } - - return crc; - } - - public static function srs_crc32(bytes:ByteArray):uint - { - return mpegts_crc32(bytes); - } - - /** - * parse the annexb header. - * @return an object which is: - * nb_header, an int start code, the header size. - * ok, a bool indicates whether the stream is annexb. - */ - public static function srs_avc_startswith_annexb(stream:ByteArray):Object - { - var nb_start_code:int = 0; - var is_annexb:Boolean = false; - - var bytes:uint = stream.position; - var p:uint = bytes; - for (;;) { - if (stream.bytesAvailable < p - bytes + 3) { - break; - } - - // not match - if (stream[p] != 0x00 || stream[p + 1] != 0x00) { - break; - } - - // match N[00] 00 00 01, where N>=0 - if (stream[p + 2] == 0x01) { - nb_start_code = p - bytes + 3; - is_annexb = true; - break; - } - - p++; - } - - return { - nb_header: nb_start_code, - ok: is_annexb - }; - } - - /** - * whether stream starts with the aac ADTS - * from aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 75, 1.A.2.2 ADTS. - * start code must be '1111 1111 1111'B, that is 0xFFF - */ - public static function srs_aac_startswith_adts(stream:ByteArray):Boolean - { - if (stream.bytesAvailable < 2) { - return false; - } - - // matched 12bits 0xFFF, - // @remark, we must cast the 0xff to char to compare. - var p:uint = stream.position; - if (stream[p] != 0xff || (stream[p + 1] & 0xf0) != 0xf0) { - return false; - } - - return true; - } - - public static function array_equals(a:ByteArray, b:ByteArray):Boolean - { - if ((a && !b) || (!b && a) || a.length != b.length) { - return false; - } - - for (var i:int = 0; i < a.length; i++) { - if (a[i] != b[i]) { - return false; - } - } - - return true; - } - - /** - * read the ue(v) of h.264 bit stream. - */ - public static function srs_avc_nalu_read_uev(stream:SrsBitStream):int - { - if (stream.empty()) { - throw new Error("avc: h.264 bit stream empty."); - } - - // ue(v) in 9.1 Parsing process for Exp-Golomb codes - // H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 227. - // Syntax elements coded as ue(v), me(v), or se(v) are Exp-Golomb-coded. - // leadingZeroBits = -1; - // for( b = 0; !b; leadingZeroBits++ ) - // b = read_bits( 1 ) - // The variable codeNum is then assigned as follows: - // codeNum = (2<= 31) { - throw new Error("avc: h.264 ue(v) overflow."); - } - - var v:int = (1 << leadingZeroBits) - 1; - for (var i:int = 0; i < leadingZeroBits; i++) { - b = stream.read_bit(); - v += b << (leadingZeroBits - 1 - i); - } - - return v; - } - - /** - * read a bit from the h.264 avc bit stream. - */ - public static function srs_avc_nalu_read_bit(stream:SrsBitStream):int - { - if (stream.empty()) { - throw new Error("avc: h.264 bit stream empty."); - } - - var v:int = stream.read_bit(); - - return v; - } - - public static function srs_print_bytes(bytes:ByteArray, len:int):void - { - var prt_len:int; - var print:String = ""; - - prt_len = len == -1? (bytes.length - bytes.position): len; - prt_len = Math.min(prt_len, Math.min((bytes.length - bytes.position), 2048)); - - for (var i:int = 0; i < prt_len; i++) - { - print += bytes[i + bytes.position].toString(16).toUpperCase() + " "; - } - trace(print); - } -} - -class SrsBitStream -{ - private var stream:ByteArray; - private var cb:uint; - private var cb_left:uint; - - public function SrsBitStream(s:ByteArray) - { - cb = 0; - cb_left = 0; - stream = s; - } - - public function empty():Boolean - { - if (cb_left) { - return false; - } - return stream.bytesAvailable == 0; - } - - public function read_bit():uint - { - if (!cb_left) { - cb = stream.readUnsignedByte(); - cb_left = 8; - } - - var v:uint = (cb >> (cb_left - 1)) & 0x01; - cb_left--; - return v; - } -} - -/** - * the raw h.264 stream, in annexb. - */ -class SrsRawH264Stream -{ - private var _log:ILogger = new TraceLogger("HLS"); - - public function SrsRawH264Stream() - { - } - - /** - * demux the stream in annexb format. - */ - public function annexb_demux(stream:ByteArray):ByteArray - { - var frame:ByteArray = new ByteArray(); - while (stream.bytesAvailable) { - // each frame must prefixed by annexb format. - // about annexb, @see H.264-AVC-ISO_IEC_14496-10.pdf, page 211. - var annexb:Object = SrsUtils.srs_avc_startswith_annexb(stream); - if (!annexb.ok) { - throw new Error("avc: not annexb format."); - } - - var nb_annexb_header:uint = annexb.nb_header; - var start:uint = stream.position + annexb.nb_header; - stream.position += annexb.nb_header; - - // find the last frame prefixed by annexb format. - while (stream.bytesAvailable) { - annexb = SrsUtils.srs_avc_startswith_annexb(stream); - if (annexb.ok) { - break; - } - stream.position++; - } - - // demux the frame. - var pos:uint = stream.position; - var nb_frame:int = pos - start; - stream.position = start; - stream.readBytes(frame, 0, nb_frame); - stream.position = pos; - _log.debug("avc: annexb {0}B header, {1}B frame", nb_annexb_header, nb_frame); - break; - } - return frame; - } - /** - * whether the frame is sps or pps. - */ - public function is_sps(frame:ByteArray):Boolean - { - // 5bits, 7.3.1 NAL unit syntax, - // H.264-AVC-ISO_IEC_14496-10.pdf, page 44. - // 7: SPS, 8: PPS, 5: I Frame, 1: P Frame - var nal_unit_type:uint = (frame[0] & 0x1f); - - return nal_unit_type == 7; - } - public function is_pps(frame:ByteArray):Boolean - { - // 5bits, 7.3.1 NAL unit syntax, - // H.264-AVC-ISO_IEC_14496-10.pdf, page 44. - // 7: SPS, 8: PPS, 5: I Frame, 1: P Frame - var nal_unit_type:uint = (frame[0] & 0x1f); - - return nal_unit_type == 8; - } - /** - * demux the sps or pps to string. - * @param sps/pps output the sps/pps. - */ - public function sps_demux(frame:ByteArray):ByteArray - { - // atleast 1bytes for SPS to decode the type, profile, constrain and level. - if (frame.bytesAvailable < 4) { - return null; - } - - return frame; - } - public function pps_demux(frame:ByteArray):ByteArray - { - if (!frame.bytesAvailable) { - return null; - } - return frame; - } - - /** - * h264 raw data to h264 packet, without flv payload header. - * mux the sps/pps to flv sequence header packet. - * @param sh output the sequence header. - */ - public function mux_sequence_header(sps:ByteArray, pps:ByteArray, dts:uint, pts:uint):ByteArray - { - var sh:ByteArray = new ByteArray(); - - // 5bytes sps/pps header: - // configurationVersion, AVCProfileIndication, profile_compatibility, - // AVCLevelIndication, lengthSizeMinusOne - // 3bytes size of sps: - // numOfSequenceParameterSets, sequenceParameterSetLength(2B) - // Nbytes of sps. - // sequenceParameterSetNALUnit - // 3bytes size of pps: - // numOfPictureParameterSets, pictureParameterSetLength - // Nbytes of pps: - // pictureParameterSetNALUnit - - // decode the SPS: - // @see: 7.3.2.1.1, H.264-AVC-ISO_IEC_14496-10-2012.pdf, page 62 - if (true) { - if (sps.length < 4) { - throw new Error("sps atleast 4bytes."); - } - var frame:ByteArray = sps; - - // @see: Annex A Profiles and levels, H.264-AVC-ISO_IEC_14496-10.pdf, page 205 - // Baseline profile profile_idc is 66(0x42). - // Main profile profile_idc is 77(0x4d). - // Extended profile profile_idc is 88(0x58). - var profile_idc:int = (int)(frame[1]); - //u_int8_t constraint_set = frame[2]; - var level_idc:int = (int)(frame[3]); - - // generate the sps/pps header - // 5.3.4.2.1 Syntax, H.264-AVC-ISO_IEC_14496-15.pdf, page 16 - // configurationVersion - sh.writeByte(0x01); - // AVCProfileIndication - sh.writeByte(profile_idc); - // profile_compatibility - sh.writeByte(0x00); - // AVCLevelIndication - sh.writeByte(level_idc); - // lengthSizeMinusOne, or NAL_unit_length, always use 4bytes size, - // so we always set it to 0x03. - sh.writeByte(0x03); - } - - // sps - if (true) { - // 5.3.4.2.1 Syntax, H.264-AVC-ISO_IEC_14496-15.pdf, page 16 - // numOfSequenceParameterSets, always 1 - sh.writeByte(0x01); - // sequenceParameterSetLength - sh.writeShort(sps.length); - // sequenceParameterSetNALUnit - sh.writeBytes(sps); - } - - // pps - if (true) { - // 5.3.4.2.1 Syntax, H.264-AVC-ISO_IEC_14496-15.pdf, page 16 - // numOfPictureParameterSets, always 1 - sh.writeByte(0x01); - // pictureParameterSetLength - sh.writeShort(pps.length); - // pictureParameterSetNALUnit - sh.writeBytes(pps); - } - - // TODO: FIXME: for more profile. - // 5.3.4.2.1 Syntax, H.264-AVC-ISO_IEC_14496-15.pdf, page 16 - // profile_idc == 100 || profile_idc == 110 || profile_idc == 122 || profile_idc == 144 - - return sh; - } - /** - * h264 raw data to h264 packet, without flv payload header. - * mux the ibp to flv ibp packet. - * @return ibp an ByteArray contains the bytes. - */ - public function mux_ipb_frame(frame:ByteArray):ByteArray - { - var ibp:ByteArray = new ByteArray(); - - // 4bytes size of nalu: - // NALUnitLength - // Nbytes of nalu. - // NALUnit - - // 5.3.4.2.1 Syntax, H.264-AVC-ISO_IEC_14496-15.pdf, page 16 - // lengthSizeMinusOne, or NAL_unit_length, always use 4bytes size - var NAL_unit_length:uint = frame.length; - - // mux the avc NALU in "ISO Base Media File Format" - // from H.264-AVC-ISO_IEC_14496-15.pdf, page 20 - // NALUnitLength - ibp.writeUnsignedInt(NAL_unit_length); - // NALUnit - ibp.writeBytes(frame); - - return ibp; - } - /** - * mux the avc video packet to flv video packet. - * @param frame_type, SrsCodecVideoAVCFrameKeyFrame or SrsCodecVideoAVCFrameInterFrame. - * @param avc_packet_type, SrsCodecVideoAVCTypeSequenceHeader or SrsCodecVideoAVCTypeNALU. - * @param video the h.264 raw data. - * @param flv output the muxed flv packet. - * @param nb_flv output the muxed flv size. - */ - public function mux_avc2flv(frame:ByteArray, frame_type:uint, avc_packet_type:uint, dts:uint, pts:uint):ByteArray - { - var flv:ByteArray = new ByteArray(); - - // for h264 in RTMP video payload, there is 5bytes header: - // 1bytes, FrameType | CodecID - // 1bytes, AVCPacketType - // 3bytes, CompositionTime, the cts. - // @see: E.4.3 Video Tags, video_file_format_spec_v10_1.pdf, page 78 - - // @see: E.4.3 Video Tags, video_file_format_spec_v10_1.pdf, page 78 - // Frame Type, Type of video frame. - // CodecID, Codec Identifier. - // set the rtmp header - flv.writeByte((frame_type << 4) | SrsConsts.SrsCodecVideoAVC); - - // AVCPacketType - flv.writeByte(avc_packet_type); - - // CompositionTime - // pts = dts + cts, or - // cts = pts - dts. - // where cts is the header in rtmp video packet payload header. - var cts:uint = pts - dts; - flv.writeByte(cts >> 16); - flv.writeByte(cts >> 8); - flv.writeByte(cts); - - // h.264 raw data. - flv.writeBytes(frame); - - return flv; - } -}; - -/** - * the header of adts sample. - */ -class SrsRawAacStreamCodec -{ - public var protection_absent:uint; - public var aac_object:SrsAacObjectType; - public var sampling_frequency_index:uint; - public var channel_configuration:uint; - public var frame_length:uint; - - public var sound_format:uint; - public var sound_rate:uint; - public var sound_size:uint; - public var sound_type:uint; - // 0 for sh; 1 for raw data. - public var aac_packet_type:uint; -}; - -/** - * the raw aac stream, in adts. - */ -class SrsRawAacStream -{ - private var _log:ILogger = new TraceLogger("HLS"); - - public function SrsRawAacStream() - { - } - - /** - * demux the stream in adts format. - * @param stream the input stream bytes. - * @return an object which is: - * frame a byte array contains the demuxed aac frame. - * code a aac stream codec info. - */ - public function adts_demux(stream:ByteArray):Object - { - var frame:ByteArray = new ByteArray(); - var codec:SrsRawAacStreamCodec = new SrsRawAacStreamCodec(); - - while (stream.bytesAvailable) { - var adts_header_start:uint = stream.position; - - // decode the ADTS. - // @see aac-iso-13818-7.pdf, page 26 - // 6.2 Audio Data Transport Stream, ADTS - // @see https://github.com/ossrs/srs/issues/212#issuecomment-64145885 - // byte_alignment() - - // adts_fixed_header: - // 12bits syncword, - // 16bits left. - // adts_variable_header: - // 28bits - // 12+16+28=56bits - // adts_error_check: - // 16bits if protection_absent - // 56+16=72bits - // if protection_absent: - // require(7bytes)=56bits - // else - // require(9bytes)=72bits - if (stream.bytesAvailable < 7) { - throw new Error("aac: adts required atleast 7bytes."); - } - - // for aac, the frame must be ADTS format. - if (!SrsUtils.srs_aac_startswith_adts(stream)) { - throw new Error("aac: adts schema invalid."); - } - - // syncword 12 bslbf - stream.readByte(); - // 4bits left. - // adts_fixed_header(), 1.A.2.2.1 Fixed Header of ADTS - // ID 1 bslbf - // layer 2 uimsbf - // protection_absent 1 bslbf - var pav:uint = (stream.readUnsignedByte() & 0x0f); - var id:uint = (uint)((pav >> 3) & 0x01); - /*int8_t layer = (pav >> 1) & 0x03;*/ - var protection_absent:uint = pav & 0x01; - - /** - * ID: MPEG identifier, set to ‘1’ if the audio data in the ADTS stream are MPEG-2 AAC (See ISO/IEC 13818-7) - * and set to ‘0’ if the audio data are MPEG-4. See also ISO/IEC 11172-3, subclause 2.4.2.3. - */ - if (id != 0x01) { - //warn("adts: id must be 1(aac), actual 0(mp4a)."); - - // well, some system always use 0, but actually is aac format. - // for example, houjian vod ts always set the aac id to 0, actually 1. - // we just ignore it, and alwyas use 1(aac) to demux. - id = 0x01; - } - - var sfiv:uint = stream.readUnsignedShort(); - // profile 2 uimsbf - // sampling_frequency_index 4 uimsbf - // private_bit 1 bslbf - // channel_configuration 3 uimsbf - // original/copy 1 bslbf - // home 1 bslbf - var profile:uint = (sfiv >> 14) & 0x03; - var sampling_frequency_index:uint = (sfiv >> 10) & 0x0f; - /*int8_t private_bit = (sfiv >> 9) & 0x01;*/ - var channel_configuration:uint = (sfiv >> 6) & 0x07; - /*int8_t original = (sfiv >> 5) & 0x01;*/ - /*int8_t home = (sfiv >> 4) & 0x01;*/ - //int8_t Emphasis; @remark, Emphasis is removed, @see https://github.com/ossrs/srs/issues/212#issuecomment-64154736 - // 4bits left. - // adts_variable_header(), 1.A.2.2.2 Variable Header of ADTS - // copyright_identification_bit 1 bslbf - // copyright_identification_start 1 bslbf - /*int8_t fh_copyright_identification_bit = (fh1 >> 3) & 0x01;*/ - /*int8_t fh_copyright_identification_start = (fh1 >> 2) & 0x01;*/ - // frame_length 13 bslbf: Length of the frame including headers and error_check in bytes. - // use the left 2bits as the 13 and 12 bit, - // the frame_length is 13bits, so we move 13-2=11. - var frame_length:uint = (sfiv << 11) & 0x1800; - - // skip -1 to read 4B for actually read 3B - stream.position -= 1; - var abfv:uint = (stream.readUnsignedInt() & 0x00ffffff); - // frame_length 13 bslbf: consume the first 13-2=11bits - // the fh2 is 24bits, so we move right 24-11=13. - frame_length |= (abfv >> 13) & 0x07ff; - // adts_buffer_fullness 11 bslbf - /*int16_t fh_adts_buffer_fullness = (abfv >> 2) & 0x7ff;*/ - // number_of_raw_data_blocks_in_frame 2 uimsbf - /*int16_t number_of_raw_data_blocks_in_frame = abfv & 0x03;*/ - // adts_error_check(), 1.A.2.2.3 Error detection - if (!protection_absent) { - if (stream.bytesAvailable < 2) { - throw new Error("aac: adts header corrupt."); - } - // crc_check 16 Rpchof - /*int16_t crc_check = */ stream.readUnsignedShort(); - } - - // TODO: check the sampling_frequency_index - // TODO: check the channel_configuration - - // raw_data_blocks - var adts_header_size:uint = stream.position - adts_header_start; - var raw_data_size:uint = frame_length - adts_header_size; - if (stream.bytesAvailable < raw_data_size) { - throw new Error("aac: adts raw data corrupt."); - } - - // the codec info. - codec.protection_absent = protection_absent; - codec.aac_object = SrsAacProfile.parse(profile).toRtmpObjectType(); - codec.sampling_frequency_index = sampling_frequency_index; - codec.channel_configuration = channel_configuration; - codec.frame_length = frame_length; - - // @see srs_audio_write_raw_frame(). - codec.sound_format = 10; // AAC - // TODO: FIXME: maybe need to resample audio. - if (sampling_frequency_index <= 0x0c && sampling_frequency_index > 0x0a) { - codec.sound_rate = SrsConsts.SrsCodecAudioSampleRate5512; - } else if (sampling_frequency_index <= 0x0a && sampling_frequency_index > 0x07) { - codec.sound_rate = SrsConsts.SrsCodecAudioSampleRate11025; - } else if (sampling_frequency_index <= 0x07 && sampling_frequency_index > 0x04) { - codec.sound_rate = SrsConsts.SrsCodecAudioSampleRate22050; - } else if (sampling_frequency_index <= 0x04) { - codec.sound_rate = SrsConsts.SrsCodecAudioSampleRate44100; - } else { - codec.sound_rate = SrsConsts.SrsCodecAudioSampleRate44100; - _log.warn("adts invalid sample rate for flv, rate=%{0}", sampling_frequency_index); - } - codec.sound_type = (uint)(Math.max(0, Math.min(1, channel_configuration - 1))); - // TODO: FIXME: finger it out the sound size by adts. - codec.sound_size = 1; // 0(8bits) or 1(16bits). - - // frame data. - stream.readBytes(frame, 0, raw_data_size); - - break; - } - - return { - frame: frame, - codec: codec - }; - } - /** - * aac raw data to aac packet, without flv payload header. - * mux the aac specific config to flv sequence header packet. - * @param sh output the sequence header. - */ - public function mux_sequence_header(codec:SrsRawAacStreamCodec):ByteArray - { - var sh:ByteArray = new ByteArray(); - - // only support aac profile 1-4. - if (codec.aac_object == SrsAacObjectType.Reserved) { - throw new Error("aac: profile invalid."); - } - - var audioObjectType:SrsAacObjectType = codec.aac_object; - var channelConfiguration:uint = codec.channel_configuration; - var samplingFrequencyIndex:uint = codec.sampling_frequency_index; - - // override the aac samplerate by user specified. - // @see https://github.com/ossrs/srs/issues/212#issuecomment-64146899 - switch (codec.sound_rate) { - case SrsConsts.SrsCodecAudioSampleRate11025: - samplingFrequencyIndex = 0x0a; break; - case SrsConsts.SrsCodecAudioSampleRate22050: - samplingFrequencyIndex = 0x07; break; - case SrsConsts.SrsCodecAudioSampleRate44100: - samplingFrequencyIndex = 0x04; break; - default: - break; - } - - var ch:uint = 0; - // @see aac-mp4a-format-ISO_IEC_14496-3+2001.pdf - // AudioSpecificConfig (), page 33 - // 1.6.2.1 AudioSpecificConfig - // audioObjectType; 5 bslbf - ch = (audioObjectType.toInt() << 3) & 0xf8; - // 3bits left. - - // samplingFrequencyIndex; 4 bslbf - ch |= (samplingFrequencyIndex >> 1) & 0x07; - sh.writeByte(ch); - ch = (samplingFrequencyIndex << 7) & 0x80; - if (samplingFrequencyIndex == 0x0f) { - throw new Error("aac: sample rate invalid."); - } - // 7bits left. - - // channelConfiguration; 4 bslbf - ch |= (channelConfiguration << 3) & 0x78; - // 3bits left. - - // GASpecificConfig(), page 451 - // 4.4.1 Decoder configuration (GASpecificConfig) - // frameLengthFlag; 1 bslbf - // dependsOnCoreCoder; 1 bslbf - // extensionFlag; 1 bslbf - sh.writeByte(ch); - - return sh; - } - /** - * mux the aac audio packet to flv audio packet. - * @param frame the aac raw data. - * @param nb_frame the count of aac frame. - * @param codec the codec info of aac. - * @param flv output the muxed flv packet. - * @param nb_flv output the muxed flv size. - */ - public function mux_aac2flv(frame:ByteArray, codec:SrsRawAacStreamCodec, dts:uint):ByteArray - { - var flv:ByteArray = new ByteArray(); - - var sound_format:uint = codec.sound_format; - var sound_type:uint = codec.sound_type; - var sound_size:uint = codec.sound_size; - var sound_rate:uint = codec.sound_rate; - var aac_packet_type:uint = codec.aac_packet_type; - - // for audio frame, there is 1 or 2 bytes header: - // 1bytes, SoundFormat|SoundRate|SoundSize|SoundType - // 1bytes, AACPacketType for SoundFormat == 10, 0 is sequence header. - - var audio_header:uint = sound_type & 0x01; - audio_header |= (sound_size << 1) & 0x02; - audio_header |= (sound_rate << 2) & 0x0c; - audio_header |= (sound_format << 4) & 0xf0; - - flv.writeByte(audio_header); - - if (sound_format == SrsConsts.SrsCodecAudioAAC) { - flv.writeByte(aac_packet_type); - } - - flv.writeBytes(frame); - - return flv; - } -}; - -/** - * the fake enum. - */ -class SrsEnum -{ - protected var value:int; - - public function SrsEnum(v:int) - { - value = v; - } - - public function equals(e:SrsEnum):Boolean - { - return value == e.value; - } - - public function notEquals(e:SrsEnum):Boolean - { - return value != e.value; - } - - public function equalsValue(v:int):Boolean - { - return value == v; - } - - public function toString():String - { - return String(value); - } - - public function toInt():int - { - return value; - } -} - -/** - * the aac profile, for ADTS(HLS/TS) - * @see https://github.com/ossrs/srs/issues/310 - */ -class SrsAacProfile extends SrsEnum -{ - public function SrsAacProfile(v:int) - { - super(v); - } - public static function parse(v:int):SrsAacProfile - { - switch (v) { - case 0x00: return SrsAacProfile.Main; - case 0x01: return SrsAacProfile.LC; - case 0x02: return SrsAacProfile.SSR; - default: case 0x03: return SrsAacProfile.Reserved; - } - } - - public static const Reserved:SrsAacProfile = new SrsAacProfile(0x03); - - // @see 7.1 Profiles, aac-iso-13818-7.pdf, page 40 - public static const Main:SrsAacProfile = new SrsAacProfile(0x00); - public static const LC:SrsAacProfile = new SrsAacProfile(0x01); - public static const SSR:SrsAacProfile = new SrsAacProfile(0x02); - - // ts/hls/adts audio header profile to RTMP sequence header object type. - public function toRtmpObjectType():SrsAacObjectType - { - if (SrsAacProfile.Main.equals(this)) { - return SrsAacObjectType.AacMain; - } else if (SrsAacProfile.LC.equals(this)) { - return SrsAacObjectType.AacLC; - } else if (SrsAacProfile.SSR.equals(this)) { - return SrsAacObjectType.AacSSR; - } else { - return SrsAacObjectType.Reserved; - } - } -}; - -/** - * the aac object type, for RTMP sequence header - * for AudioSpecificConfig, @see aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 33 - * for audioObjectType, @see aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 23 - */ -class SrsAacObjectType extends SrsEnum -{ - public function SrsAacObjectType(v:int) - { - super(v); - } - public static function parse(v:int):SrsAacObjectType - { - switch (v) { - case 0x01: return SrsAacObjectType.AacMain; - case 0x02: return SrsAacObjectType.AacLC; - case 0x03: return SrsAacObjectType.AacSSR; - case 0x05: return SrsAacObjectType.AacHE; - case 0x29: return SrsAacObjectType.AacHEV2; - default: case 0x00: return SrsAacObjectType.Reserved; - } - } - - public static const Reserved:SrsAacObjectType = new SrsAacObjectType(0x00); - - // Table 1.1 – Audio Object Type definition - // @see @see aac-mp4a-format-ISO_IEC_14496-3+2001.pdf, page 23 - public static const AacMain:SrsAacObjectType = new SrsAacObjectType(0x01); - public static const AacLC:SrsAacObjectType = new SrsAacObjectType(0x02); - public static const AacSSR:SrsAacObjectType = new SrsAacObjectType(0x03); - - // AAC HE = LC+SBR - public static const AacHE:SrsAacObjectType = new SrsAacObjectType(0x05); - // AAC HEv2 = LC+SBR+PS - public static const AacHEV2:SrsAacObjectType = new SrsAacObjectType(0x29); - - // RTMP sequence header object type to ts/hls/adts audio header profile. - public function toTsProfile():SrsAacProfile - { - if (SrsAacObjectType.AacMain.equals(this)) { - return SrsAacProfile.Main; - } else if (SrsAacObjectType.AacLC.equals(this)) { - return SrsAacProfile.LC; - } else if (SrsAacObjectType.AacHE.equals(this)) { - return SrsAacProfile.LC; - } else if (SrsAacObjectType.AacHEV2.equals(this)) { - return SrsAacProfile.LC; - } else if (SrsAacObjectType.AacSSR.equals(this)) { - return SrsAacProfile.SSR; - } else { - return SrsAacProfile.Reserved; - } - } -}; - -/** - * the pid of ts packet, - * Table 2-3 - PID table, hls-mpeg-ts-iso13818-1.pdf, page 37 - * NOTE - The transport packets with PID values 0x0000, 0x0001, and 0x0010-0x1FFE are allowed to carry a PCR. - */ -class SrsTsPid extends SrsEnum -{ - public function SrsTsPid(v:int) - { - super(v); - } - public static function parse(v:int):SrsTsPid - { - switch (v) { - case 0x00: return SrsTsPid.PAT; - case 0x01: return SrsTsPid.CAT; - case 0x02: return SrsTsPid.TSDT; - case 0x03: return SrsTsPid.ReservedStart; - case 0x0f: return SrsTsPid.ReservedEnd; - case 0x10: return SrsTsPid.AppStart; - case 0x1ffe: return SrsTsPid.AppEnd; - case 0x01FFF: return SrsTsPid.NULL; - default: return new SrsTsPid(v); - } - } - - // Program Association Table(see Table 2-25). - public static const PAT:SrsTsPid = new SrsTsPid(0x00); - // Conditional Access Table (see Table 2-27). - public static const CAT:SrsTsPid = new SrsTsPid(0x01); - // Transport Stream Description Table - public static const TSDT:SrsTsPid = new SrsTsPid(0x02); - // Reserved - public static const ReservedStart:SrsTsPid = new SrsTsPid(0x03); - public static const ReservedEnd:SrsTsPid = new SrsTsPid(0x0f); - // May be assigned as network_PID, Program_map_PID, elementary_PID, or for other purposes - public static const AppStart:SrsTsPid = new SrsTsPid(0x10); - public static const AppEnd:SrsTsPid = new SrsTsPid(0x1ffe); - // null packets (see Table 2-3) - public static const NULL:SrsTsPid = new SrsTsPid(0x01FFF); -}; - -/** - * the transport_scrambling_control of ts packet, - * Table 2-4 - Scrambling control values, hls-mpeg-ts-iso13818-1.pdf, page 38 - */ -class SrsTsScrambled extends SrsEnum -{ - public function SrsTsScrambled(v:int) - { - super(v); - } - public static function parse(v:int):SrsTsScrambled - { - switch (v) { - case 0x01: return SrsTsScrambled.UserDefined1; - case 0x02: return SrsTsScrambled.UserDefined2; - case 0x03: return SrsTsScrambled.UserDefined3; - default: case 0x00: return SrsTsScrambled.Disabled; - } - } - - // Not scrambled - public static const Disabled:SrsTsScrambled = new SrsTsScrambled(0x00); - // User-defined - public static const UserDefined1:SrsTsScrambled = new SrsTsScrambled(0x01); - // User-defined - public static const UserDefined2:SrsTsScrambled = new SrsTsScrambled(0x02); - // User-defined - public static const UserDefined3:SrsTsScrambled = new SrsTsScrambled(0x03); -}; - -/** - * the adaption_field_control of ts packet, - * Table 2-5 - Adaptation field control values, hls-mpeg-ts-iso13818-1.pdf, page 38 - */ -class SrsTsAdaptationFieldType extends SrsEnum -{ - public function SrsTsAdaptationFieldType(v:int) - { - super(v); - } - public static function parse(v:int):SrsTsAdaptationFieldType - { - switch (v) { - case 0x01: return SrsTsAdaptationFieldType.PayloadOnly; - case 0x02: return SrsTsAdaptationFieldType.AdaptionOnly; - case 0x03: return SrsTsAdaptationFieldType.Both; - default: case 0x00: return SrsTsAdaptationFieldType.Reserved; - } - } - - // Reserved for future use by ISO/IEC - public static const Reserved:SrsTsAdaptationFieldType = new SrsTsAdaptationFieldType(0x00); - // No adaptation_field, payload only - public static const PayloadOnly:SrsTsAdaptationFieldType = new SrsTsAdaptationFieldType(0x01); - // Adaptation_field only, no payload - public static const AdaptionOnly:SrsTsAdaptationFieldType = new SrsTsAdaptationFieldType(0x02); - // Adaptation_field followed by payload - public static const Both:SrsTsAdaptationFieldType = new SrsTsAdaptationFieldType(0x03); -}; - -/** - * the actually parsed ts pid, - * @see SrsTsPid, some pid, for example, PMT/Video/Audio is specified by PAT or other tables. - */ -class SrsTsPidApply extends SrsEnum -{ - public function SrsTsPidApply(v:int) - { - super(v); - } - - public static const Reserved:SrsTsPidApply = new SrsTsPidApply(0x00); // TSPidTypeReserved, nothing parsed, used reserved. - public static const PAT:SrsTsPidApply = new SrsTsPidApply(0x01); // Program associtate table - public static const PMT:SrsTsPidApply = new SrsTsPidApply(0x02); // Program map table. - - public static const Video:SrsTsPidApply = new SrsTsPidApply(0x03); // for video - public static const Audio:SrsTsPidApply = new SrsTsPidApply(0x04); // vor audio -}; - -/** - * Table 2-29 - Stream type assignments - */ -class SrsTsStream extends SrsEnum -{ - public function SrsTsStream(v:int) - { - super(v); - } - public static function parse(v:int):SrsTsStream - { - switch (v) { - case 0x8a: return SrsTsStream.AudioDTS; - case 0x80: return SrsTsStream.AudioAC3; - case 0x1b: return SrsTsStream.VideoH264; - case 0x11: return SrsTsStream.AudioMpeg4; - case 0x10: return SrsTsStream.VideoMpeg4; - case 0x0f: return SrsTsStream.AudioAAC; - case 0x04: return SrsTsStream.AudioMp3; - default: case 0x00: return SrsTsStream.Reserved; - } - } - - // ITU-T | ISO/IEC Reserved - public static const Reserved:SrsTsStream = new SrsTsStream(0x00); - // ISO/IEC 11172 Video - // ITU-T Rec. H.262 | ISO/IEC 13818-2 Video or ISO/IEC 11172-2 constrained parameter video stream - // ISO/IEC 11172 Audio - // ISO/IEC 13818-3 Audio - public static const AudioMp3:SrsTsStream = new SrsTsStream(0x04); - // ITU-T Rec. H.222.0 | ISO/IEC 13818-1 private_sections - // ITU-T Rec. H.222.0 | ISO/IEC 13818-1 PES packets containing private data - // ISO/IEC 13522 MHEG - // ITU-T Rec. H.222.0 | ISO/IEC 13818-1 Annex A DSM-CC - // ITU-T Rec. H.222.1 - // ISO/IEC 13818-6 type A - // ISO/IEC 13818-6 type B - // ISO/IEC 13818-6 type C - // ISO/IEC 13818-6 type D - // ITU-T Rec. H.222.0 | ISO/IEC 13818-1 auxiliary - // ISO/IEC 13818-7 Audio with ADTS transport syntax - public static const AudioAAC:SrsTsStream = new SrsTsStream(0x0f); - // ISO/IEC 14496-2 Visual - public static const VideoMpeg4:SrsTsStream = new SrsTsStream(0x10); - // ISO/IEC 14496-3 Audio with the LATM transport syntax as defined in ISO/IEC 14496-3 / AMD 1 - public static const AudioMpeg4:SrsTsStream = new SrsTsStream(0x11); - // ISO/IEC 14496-1 SL-packetized stream or FlexMux stream carried in PES packets - // ISO/IEC 14496-1 SL-packetized stream or FlexMux stream carried in ISO/IEC14496_sections. - // ISO/IEC 13818-6 Synchronized Download Protocol - // ITU-T Rec. H.222.0 | ISO/IEC 13818-1 Reserved - // 0x15-0x7F - public static const VideoH264:SrsTsStream = new SrsTsStream(0x1b); - // User Private - // 0x80-0xFF - public static const AudioAC3:SrsTsStream = new SrsTsStream(0x80); - public static const AudioDTS:SrsTsStream = new SrsTsStream(0x8a); -}; - -/** - * the ts channel. - */ -class SrsTsChannel -{ - public var pid:int; - public var apply:SrsTsPidApply; - public var stream:SrsTsStream; - public var msg:SrsTsMessage; - public var context:SrsTsContext; - - public function SrsTsChannel() - { - pid = 0; - apply = SrsTsPidApply.Reserved; - stream = SrsTsStream.Reserved; - msg = null; - context = null; - } -}; - -/** - * the stream_id of PES payload of ts packet. - * Table 2-18 – Stream_id assignments, hls-mpeg-ts-iso13818-1.pdf, page 52. - */ -class SrsTsPESStreamId extends SrsEnum -{ - public function SrsTsPESStreamId(v:int) - { - super(v); - } - public static function parse(v:int):SrsTsPESStreamId - { - switch (v) { - case 0xbc: return SrsTsPESStreamId.ProgramStreamMap; - case 0xbd: return SrsTsPESStreamId.PrivateStream1; - case 0xbe: return SrsTsPESStreamId.PaddingStream; - case 0xbf: return SrsTsPESStreamId.PrivateStream2; - case 0x06: return SrsTsPESStreamId.AudioChecker; - case 0xc0: return SrsTsPESStreamId.AudioCommon; - case 0x0e: return SrsTsPESStreamId.VideoChecker; - case 0xe0: return SrsTsPESStreamId.VideoCommon; - case 0xf0: return SrsTsPESStreamId.EcmStream; - case 0xf1: return SrsTsPESStreamId.EmmStream; - case 0xf2: return SrsTsPESStreamId.DsmccStream; - case 0xf3: return SrsTsPESStreamId._13522Stream; - case 0xf4: return SrsTsPESStreamId.H2221TypeA; - case 0xf5: return SrsTsPESStreamId.H2221TypeB; - case 0xf6: return SrsTsPESStreamId.H2221TypeC; - case 0xf7: return SrsTsPESStreamId.H2221TypeD; - case 0xf8: return SrsTsPESStreamId.H2221TypeE; - case 0xf9: return SrsTsPESStreamId.AncillaryStream; - case 0xfa: return SrsTsPESStreamId.SlPacketizedStream; - case 0xfb: return SrsTsPESStreamId.FlexMuxStream; - case 0xff: return SrsTsPESStreamId.ProgramStreamDirectory; - default: case 0x00: return SrsTsPESStreamId.Reserved; - } - } - - // reserved - public static const Reserved:SrsTsPESStreamId = new SrsTsPESStreamId(0x00); - - // program_stream_map - public static const ProgramStreamMap:SrsTsPESStreamId = new SrsTsPESStreamId(0xbc); - // private_stream_1 - public static const PrivateStream1:SrsTsPESStreamId = new SrsTsPESStreamId(0xbd); - // padding_stream - public static const PaddingStream:SrsTsPESStreamId = new SrsTsPESStreamId(0xbe); - // private_stream_2 - public static const PrivateStream2:SrsTsPESStreamId = new SrsTsPESStreamId(0xbf); - - // 110x xxxx - // ISO/IEC 13818-3 or ISO/IEC 11172-3 or ISO/IEC 13818-7 or ISO/IEC - // 14496-3 audio stream number x xxxx - // ((sid >> 5) & 0x07) == SrsTsPESStreamIdAudio - // @remark, use SrsTsPESStreamIdAudioCommon as actually audio, SrsTsPESStreamIdAudio to check whether audio. - public static const AudioChecker:SrsTsPESStreamId = new SrsTsPESStreamId(0x06); - public static const AudioCommon:SrsTsPESStreamId = new SrsTsPESStreamId(0xc0); - - // 1110 xxxx - // ITU-T Rec. H.262 | ISO/IEC 13818-2 or ISO/IEC 11172-2 or ISO/IEC - // 14496-2 video stream number xxxx - // ((stream_id >> 4) & 0x0f) == SrsTsPESStreamIdVideo - // @remark, use SrsTsPESStreamIdVideoCommon as actually video, SrsTsPESStreamIdVideo to check whether video. - public static const VideoChecker:SrsTsPESStreamId = new SrsTsPESStreamId(0x0e); - public static const VideoCommon:SrsTsPESStreamId = new SrsTsPESStreamId(0xe0); - - // ECM_stream - public static const EcmStream:SrsTsPESStreamId = new SrsTsPESStreamId(0xf0); - // EMM_stream - public static const EmmStream:SrsTsPESStreamId = new SrsTsPESStreamId(0xf1); - // DSMCC_stream - public static const DsmccStream:SrsTsPESStreamId = new SrsTsPESStreamId(0xf2); - // 13522_stream - public static const _13522Stream:SrsTsPESStreamId = new SrsTsPESStreamId(0xf3); - // H_222_1_type_A - public static const H2221TypeA:SrsTsPESStreamId = new SrsTsPESStreamId(0xf4); - // H_222_1_type_B - public static const H2221TypeB:SrsTsPESStreamId = new SrsTsPESStreamId(0xf5); - // H_222_1_type_C - public static const H2221TypeC:SrsTsPESStreamId = new SrsTsPESStreamId(0xf6); - // H_222_1_type_D - public static const H2221TypeD:SrsTsPESStreamId = new SrsTsPESStreamId(0xf7); - // H_222_1_type_E - public static const H2221TypeE:SrsTsPESStreamId = new SrsTsPESStreamId(0xf8); - // ancillary_stream - public static const AncillaryStream:SrsTsPESStreamId = new SrsTsPESStreamId(0xf9); - // SL_packetized_stream - public static const SlPacketizedStream:SrsTsPESStreamId = new SrsTsPESStreamId(0xfa); - // FlexMux_stream - public static const FlexMuxStream:SrsTsPESStreamId = new SrsTsPESStreamId(0xfb); - // reserved data stream - // 1111 1100 … 1111 1110 - // program_stream_directory - public static const ProgramStreamDirectory:SrsTsPESStreamId = new SrsTsPESStreamId(0xff); -}; - -/** - * the media audio/video message parsed from PES packet. - */ -class SrsTsMessage -{ - // decoder only, - // the ts messgae does not use them, - // for user to get the channel and packet. - public var channel:SrsTsChannel; - public var packet:SrsTsPacket; - // the audio cache buffer start pts, to flush audio if full. - // @remark the pts is not the adjust one, it's the orignal pts. - public var start_pts:Number; - // whether this message with pcr info, - // generally, the video IDR(I frame, the keyframe of h.264) carray the pcr info. - public var write_pcr:Boolean; - // whether got discontinuity ts, for example, sequence header changed. - public var discontinuity:Boolean; - // the timestamp in 90khz - public var dts:Number; - public var pts:Number; - // the id of pes stream to indicates the payload codec. - // @remark use is_audio() and is_video() to check it, and stream_number() to finger it out. - public var sid:SrsTsPESStreamId; - // the size of payload, 0 indicates the length() of payload. - public var PES_packet_length:uint; - // the chunk id. - public var continuity_counter:uint; - // the payload bytes. - public var payload:ByteArray; - - public function SrsTsMessage(c:SrsTsChannel, p:SrsTsPacket) - { - channel = c; - packet = p; - - dts = pts = 0; - sid = SrsTsPESStreamId.Reserved; - continuity_counter = 0; - PES_packet_length = 0; - payload = new ByteArray(); - - start_pts = 0; - write_pcr = false; - } - - // decoder - /** - * dumps all bytes in stream to ts message. - */ - public function dump(stream:ByteArray):int - { - if (!stream.bytesAvailable) { - return 0; - } - - // xB - var nb_bytes:int = stream.length - stream.position; - if (PES_packet_length > 0) { - nb_bytes = (int)(Math.min(nb_bytes, PES_packet_length - payload.length)); - } - - if (nb_bytes > 0) { - if (stream.bytesAvailable < nb_bytes) { - throw new Error("ts: dump PSE bytes failed, requires=" + nb_bytes + " bytes"); - } - - stream.readBytes(payload, payload.length, nb_bytes); - } - - return nb_bytes; - } - /** - * whether ts message is completed to reap. - * @param payload_unit_start_indicator whether new ts message start. - * PES_packet_length is 0, the payload_unit_start_indicator=1 to reap ts message. - * PES_packet_length > 0, the payload.length() == PES_packet_length to reap ts message. - * @remark when PES_packet_length>0, the payload_unit_start_indicator should never be 1 when not completed. - * @remark when fresh, the payload_unit_start_indicator should be 1. - */ - public function completed(payload_unit_start_indicator:Number):Boolean - { - if (PES_packet_length == 0) { - return payload_unit_start_indicator != 0; - } - return payload.length >= PES_packet_length; - } - /** - * whether the message is fresh. - */ - public function fresh():Boolean - { - return payload.length == 0; - } - - /** - * whether the sid indicates the elementary stream audio. - */ - public function is_audio():Boolean - { - var sidValue:int = (sid.toInt() >> 5) & 0x07; - return SrsTsPESStreamId.AudioChecker.equalsValue(sidValue); - } - /** - * whether the sid indicates the elementary stream video. - */ - public function is_video():Boolean - { - var sidValue:int = (sid.toInt() >> 4) & 0x0f; - return SrsTsPESStreamId.VideoChecker.equalsValue(sidValue); - } - /** - * when audio or video, get the stream number which specifies the format of stream. - * @return the stream number for audio/video; otherwise, -1. - */ - public function stream_number():Number - { - if (is_audio()) { - return sid.toInt() & 0x1f; - } else if (is_video()) { - return sid.toInt() & 0x0f; - } - return -1; - } -}; - -/** - * the ts message handler. - */ -interface ISrsTsHandler -{ - /** - * when ts context got message, use handler to process it. - * @param msg the ts msg, user should never free it. - * @return an int error code. - */ - function on_ts_message(msg:SrsTsMessage):void; -}; - -/** - * the context of ts, to decode the ts stream. - */ -class SrsTsContext -{ - private var _hls:HlsCodec; - - // codec - // key, a Number indicates the pid, - // value, the SrsTsChannel object. - private var _pids:Dict; - - // whether hls pure audio stream. - private var _pure_audio:Boolean; - - public function SrsTsContext(hls:HlsCodec) - { - _hls = hls; - _pure_audio = false; - _pids = new Dict(); - } - - /** - * whether the hls stream is pure audio stream. - */ - public function is_pure_audio():Boolean - { - return _pure_audio; - } - - /** - * when PMT table parsed, we know some info about stream. - */ - public function on_pmt_parsed():void - { - _pure_audio = true; - - var keys:Array = _pids.keys(); - for (var i:int = 0; i < keys.length; i++) { - var channel:SrsTsChannel = _pids.get(keys[i]) as SrsTsChannel; - if (channel.apply == SrsTsPidApply.Video) { - _pure_audio = false; - } - } - } - - // codec - /** - * get the pid apply, the parsed pid. - * @return the apply channel; NULL for invalid. - */ - public function getChannel(pid:Number):SrsTsChannel - { - if (!_pids.has(pid)) { - return null; - } - return _pids.get(pid) as SrsTsChannel; - } - - /** - * set the pid apply, the parsed pid. - */ - public function setChannel(pid:Number, apply_pid:SrsTsPidApply, stream:SrsTsStream):void - { - var channel:SrsTsChannel = null; - if (!_pids.get(pid)) { - channel = new SrsTsChannel(); - channel.context = this; - _pids.set(pid, channel); - } else { - channel = _pids.get(pid) as SrsTsChannel; - } - - channel.pid = pid; - channel.apply = apply_pid; - channel.stream = stream; - } - - // decode methods - /** - * the stream contains only one ts packet. - * @param handler the ts message handler to process the msg. - * @remark we will consume all bytes in stream. - */ - public function decode(stream:ByteArray, handler:ISrsTsHandler):void - { - // parse util EOF of stream. - // for example, parse multiple times for the PES_packet_length(0) packet. - while (stream.bytesAvailable) { - var packet:SrsTsPacket = new SrsTsPacket(this); - var msg:SrsTsMessage = packet.decode(stream); - - if (!msg) { - continue; - } - - handler.on_ts_message(msg); - } - } -}; - -/** - * the packet in ts stream, - * 2.4.3.2 Transport Stream packet layer, hls-mpeg-ts-iso13818-1.pdf, page 36 - * Transport Stream packets shall be 188 bytes long. - */ -class SrsTsPacket -{ - // 1B - /** - * The sync_byte is a fixed 8-bit field whose value is '0100 0111' (0x47) or '0111 0100' (0x74). Sync_byte emulation in the choice of - * values for other regularly occurring fields, such as PID, should be avoided. - */ - public var sync_byte:int; //8bits - - // 2B - /** - * The transport_error_indicator is a 1-bit flag. When set to '1' it indicates that at least - * 1 uncorrectable bit error exists in the associated Transport Stream packet. This bit may be set to '1' by entities external to - * the transport layer. When set to '1' this bit shall not be reset to '0' unless the bit value(s) in error have been corrected. - */ - public var transport_error_indicator:int; //1bit - /** - * The payload_unit_start_indicator is a 1-bit flag which has normative meaning for - * Transport Stream packets that carry PES packets (refer to 2.4.3.6) or PSI data (refer to 2.4.4). - * - * When the payload of the Transport Stream packet contains PES packet data, the payload_unit_start_indicator has the - * following significance: a '1' indicates that the payload of this Transport Stream packet will commence(start) with the first byte - * of a PES packet and a '0' indicates no PES packet shall start in this Transport Stream packet. If the - * payload_unit_start_indicator is set to '1', then one and only one PES packet starts in this Transport Stream packet. This - * also applies to private streams of stream_type 6 (refer to Table 2-29). - * - * When the payload of the Transport Stream packet contains PSI data, the payload_unit_start_indicator has the following - * significance: if the Transport Stream packet carries the first byte of a PSI section, the payload_unit_start_indicator value - * shall be '1', indicating that the first byte of the payload of this Transport Stream packet carries the pointer_field. If the - * Transport Stream packet does not carry the first byte of a PSI section, the payload_unit_start_indicator value shall be '0', - * indicating that there is no pointer_field in the payload. Refer to 2.4.4.1 and 2.4.4.2. This also applies to private streams of - * stream_type 5 (refer to Table 2-29). - * - * For null packets the payload_unit_start_indicator shall be set to '0'. - * - * The meaning of this bit for Transport Stream packets carrying only private data is not defined in this Specification. - */ - public var payload_unit_start_indicator:int; //1bit - /** - * The transport_priority is a 1-bit indicator. When set to '1' it indicates that the associated packet is - * of greater priority than other packets having the same PID which do not have the bit set to '1'. The transport mechanism - * can use this to prioritize its data within an elementary stream. Depending on the application the transport_priority field - * may be coded regardless of the PID or within one PID only. This field may be changed by channel specific encoders or - * decoders. - */ - public var transport_priority:int; //1bit - /** - * The PID is a 13-bit field, indicating the type of the data stored in the packet payload. PID value 0x0000 is - * reserved for the Program Association Table (see Table 2-25). PID value 0x0001 is reserved for the Conditional Access - * Table (see Table 2-27). PID values 0x0002 - 0x000F are reserved. PID value 0x1FFF is reserved for null packets (see - * Table 2-3). - */ - public var pid:int; //13bits - - // 1B - /** - * This 2-bit field indicates the scrambling mode of the Transport Stream packet payload. - * The Transport Stream packet header, and the adaptation field when present, shall not be scrambled. In the case of a null - * packet the value of the transport_scrambling_control field shall be set to '00' (see Table 2-4). - */ - public var transport_scrambling_control:SrsTsScrambled; //2bits - /** - * This 2-bit field indicates whether this Transport Stream packet header is followed by an - * adaptation field and/or payload (see Table 2-5). - * - * ITU-T Rec. H.222.0 | ISO/IEC 13818-1 decoders shall discard Transport Stream packets with the - * adaptation_field_control field set to a value of '00'. In the case of a null packet the value of the adaptation_field_control - * shall be set to '01'. - */ - public var adaption_field_control:SrsTsAdaptationFieldType; //2bits - /** - * The continuity_counter is a 4-bit field incrementing with each Transport Stream packet with the - * same PID. The continuity_counter wraps around to 0 after its maximum value. The continuity_counter shall not be - * incremented when the adaptation_field_control of the packet equals '00'(reseverd) or '10'(adaptation field only). - * - * In Transport Streams, duplicate packets may be sent as two, and only two, consecutive Transport Stream packets of the - * same PID. The duplicate packets shall have the same continuity_counter value as the original packet and the - * adaptation_field_control field shall be equal to '01'(payload only) or '11'(both). In duplicate packets each byte of the original packet shall be - * duplicated, with the exception that in the program clock reference fields, if present, a valid value shall be encoded. - * - * The continuity_counter in a particular Transport Stream packet is continuous when it differs by a positive value of one - * from the continuity_counter value in the previous Transport Stream packet of the same PID, or when either of the nonincrementing - * conditions (adaptation_field_control set to '00' or '10', or duplicate packets as described above) are met. - * The continuity counter may be discontinuous when the discontinuity_indicator is set to '1' (refer to 2.4.3.4). In the case of - * a null packet the value of the continuity_counter is undefined. - */ - public var continuity_counter:int; //4bits - - private var adaptation_field:SrsTsAdaptationField; - private var payload:SrsTsPayload; - - public var context:SrsTsContext; - - public function SrsTsPacket(c:SrsTsContext) - { - context = c; - } - - public function decode(stream:ByteArray):SrsTsMessage - { - var pos:uint = stream.position; - - // 4B ts packet header. - if (stream.bytesAvailable < 4) { - throw new Error("ts: demux header failed"); - } - - sync_byte = stream.readUnsignedByte(); - // drm algorithms for ts packet: - // 1: "tiger", sync_byte is 0x74 - if (sync_byte != 0x47 && sync_byte != 0x74) { - throw new Error("ts: sync_bytes must be 0x47 or 0x74, actual=" + sync_byte); - } - - var pidv:int = stream.readUnsignedShort(); - transport_error_indicator = (pidv >> 15) & 0x01; - payload_unit_start_indicator = (pidv >> 14) & 0x01; - transport_priority = (pidv >> 13) & 0x01; - pid = pidv & 0x1FFF; - - var ccv:int = stream.readUnsignedByte(); - transport_scrambling_control = SrsTsScrambled.parse((ccv >> 6) & 0x03); - adaption_field_control = SrsTsAdaptationFieldType.parse((ccv >> 4) & 0x03); - continuity_counter = ccv & 0x0F; - - // TODO: FIXME: create pids map when got new pid. - - // optional: adaptation field - if (adaption_field_control == SrsTsAdaptationFieldType.AdaptionOnly - || adaption_field_control == SrsTsAdaptationFieldType.Both - ) { - adaptation_field = new SrsTsAdaptationField(this); - adaptation_field.decode(stream); - } - - // calc the user defined data size for payload. - var nb_payload:int = HlsCodec.SRS_TS_PACKET_SIZE - (stream.position - pos); - - // optional: payload. - if (adaption_field_control == SrsTsAdaptationFieldType.PayloadOnly - || adaption_field_control == SrsTsAdaptationFieldType.Both - ) { - if (SrsTsPid.PAT.equalsValue(pid)) { - // 2.4.4.3 Program association Table - payload = new SrsTsPayloadPAT(this); - } else { - var channel:SrsTsChannel = context.getChannel(pid); - if (channel && channel.apply == SrsTsPidApply.PMT) { - // 2.4.4.8 Program Map Table - payload = new SrsTsPayloadPMT(this); - } else if (channel && (channel.apply == SrsTsPidApply.Video || channel.apply == SrsTsPidApply.Audio)) { - // 2.4.3.6 PES packet - payload = new SrsTsPayloadPES(this); - } else { - // left bytes as reserved. - stream.position += nb_payload; - } - } - - if (payload) { - return payload.decode(stream); - } - } - - return null; - } - public function size():int - { - return 0; - } - - public static function create_pat(context:SrsTsContext, pmt_number:int, pmt_pid:int):SrsTsPacket - { - return null; - } - public static function create_pmt(context:SrsTsContext, - pmt_number:int, pmt_pid:int, vpid:int, vs:SrsTsStream, apid:int, ts:SrsTsStream):SrsTsPacket - { - return null; - } - public static function create_pes_first(context:SrsTsContext, - pid:int, sid:SrsTsPESStreamId, continuity_counter:uint, discontinuity:Boolean, - pcr:Number, dts:Number, pts:Number, size:int):SrsTsPacket - { - return null; - } - public static function create_pes_continue(context:SrsTsContext, - pid:int, sid:SrsTsPESStreamId, continuity_counter:int - ):SrsTsPacket - { - return null; - } -}; - -/** - * the adaption field of ts packet. - * 2.4.3.5 Semantic definition of fields in adaptation field, hls-mpeg-ts-iso13818-1.pdf, page 39 - * Table 2-6 - Transport Stream adaptation field, hls-mpeg-ts-iso13818-1.pdf, page 40 - */ -class SrsTsAdaptationField -{ - // 1B - /** - * The adaptation_field_length is an 8-bit field specifying the number of bytes in the - * adaptation_field immediately following the adaptation_field_length. The value 0 is for inserting a single stuffing byte in - * a Transport Stream packet. When the adaptation_field_control value is '11', the value of the adaptation_field_length shall - * be in the range 0 to 182. When the adaptation_field_control value is '10', the value of the adaptation_field_length shall - * be 183. For Transport Stream packets carrying PES packets, stuffing is needed when there is insufficient PES packet data - * to completely fill the Transport Stream packet payload bytes. Stuffing is accomplished by defining an adaptation field - * longer than the sum of the lengths of the data elements in it, so that the payload bytes remaining after the adaptation field - * exactly accommodates the available PES packet data. The extra space in the adaptation field is filled with stuffing bytes. - * - * This is the only method of stuffing allowed for Transport Stream packets carrying PES packets. For Transport Stream - * packets carrying PSI, an alternative stuffing method is described in 2.4.4. - */ - public var adaption_field_length:int; //8bits - // 1B - /** - * This is a 1-bit field which when set to '1' indicates that the discontinuity state is true for the - * current Transport Stream packet. When the discontinuity_indicator is set to '0' or is not present, the discontinuity state is - * false. The discontinuity indicator is used to indicate two types of discontinuities, system time-base discontinuities and - * continuity_counter discontinuities. - * - * A system time-base discontinuity is indicated by the use of the discontinuity_indicator in Transport Stream packets of a - * PID designated as a PCR_PID (refer to 2.4.4.9). When the discontinuity state is true for a Transport Stream packet of a - * PID designated as a PCR_PID, the next PCR in a Transport Stream packet with that same PID represents a sample of a - * new system time clock for the associated program. The system time-base discontinuity point is defined to be the instant - * in time when the first byte of a packet containing a PCR of a new system time-base arrives at the input of the T-STD. - * The discontinuity_indicator shall be set to '1' in the packet in which the system time-base discontinuity occurs. The - * discontinuity_indicator bit may also be set to '1' in Transport Stream packets of the same PCR_PID prior to the packet - * which contains the new system time-base PCR. In this case, once the discontinuity_indicator has been set to '1', it shall - * continue to be set to '1' in all Transport Stream packets of the same PCR_PID up to and including the Transport Stream - * packet which contains the first PCR of the new system time-base. After the occurrence of a system time-base - * discontinuity, no fewer than two PCRs for the new system time-base shall be received before another system time-base - * discontinuity can occur. Further, except when trick mode status is true, data from no more than two system time-bases - * shall be present in the set of T-STD buffers for one program at any time. - * - * Prior to the occurrence of a system time-base discontinuity, the first byte of a Transport Stream packet which contains a - * PTS or DTS which refers to the new system time-base shall not arrive at the input of the T-STD. After the occurrence of - * a system time-base discontinuity, the first byte of a Transport Stream packet which contains a PTS or DTS which refers - * to the previous system time-base shall not arrive at the input of the T-STD. - * - * A continuity_counter discontinuity is indicated by the use of the discontinuity_indicator in any Transport Stream packet. - * When the discontinuity state is true in any Transport Stream packet of a PID not designated as a PCR_PID, the - * continuity_counter in that packet may be discontinuous with respect to the previous Transport Stream packet of the same - * PID. When the discontinuity state is true in a Transport Stream packet of a PID that is designated as a PCR_PID, the - * continuity_counter may only be discontinuous in the packet in which a system time-base discontinuity occurs. A - * continuity counter discontinuity point occurs when the discontinuity state is true in a Transport Stream packet and the - * continuity_counter in the same packet is discontinuous with respect to the previous Transport Stream packet of the same - * PID. A continuity counter discontinuity point shall occur at most one time from the initiation of the discontinuity state - * until the conclusion of the discontinuity state. Furthermore, for all PIDs that are not designated as PCR_PIDs, when the - * discontinuity_indicator is set to '1' in a packet of a specific PID, the discontinuity_indicator may be set to '1' in the next - * Transport Stream packet of that same PID, but shall not be set to '1' in three consecutive Transport Stream packet of that - * same PID. - * - * For the purpose of this clause, an elementary stream access point is defined as follows: - * Video - The first byte of a video sequence header. - * Audio - The first byte of an audio frame. - * - * After a continuity counter discontinuity in a Transport packet which is designated as containing elementary stream data, - * the first byte of elementary stream data in a Transport Stream packet of the same PID shall be the first byte of an - * elementary stream access point or in the case of video, the first byte of an elementary stream access point or a - * sequence_end_code followed by an access point. Each Transport Stream packet which contains elementary stream data - * with a PID not designated as a PCR_PID, and in which a continuity counter discontinuity point occurs, and in which a - * PTS or DTS occurs, shall arrive at the input of the T-STD after the system time-base discontinuity for the associated - * program occurs. In the case where the discontinuity state is true, if two consecutive Transport Stream packets of the same - * PID occur which have the same continuity_counter value and have adaptation_field_control values set to '01' or '11', the - * second packet may be discarded. A Transport Stream shall not be constructed in such a way that discarding such a packet - * will cause the loss of PES packet payload data or PSI data. - * - * After the occurrence of a discontinuity_indicator set to '1' in a Transport Stream packet which contains PSI information, - * a single discontinuity in the version_number of PSI sections may occur. At the occurrence of such a discontinuity, a - * version of the TS_program_map_sections of the appropriate program shall be sent with section_length = = 13 and the - * current_next_indicator = = 1, such that there are no program_descriptors and no elementary streams described. This shall - * then be followed by a version of the TS_program_map_section for each affected program with the version_number - * incremented by one and the current_next_indicator = = 1, containing a complete program definition. This indicates a - * version change in PSI data. - */ - public var discontinuity_indicator:int; //1bit - /** - * The random_access_indicator is a 1-bit field that indicates that the current Transport - * Stream packet, and possibly subsequent Transport Stream packets with the same PID, contain some information to aid - * random access at this point. Specifically, when the bit is set to '1', the next PES packet to start in the payload of Transport - * Stream packets with the current PID shall contain the first byte of a video sequence header if the PES stream type (refer - * to Table 2-29) is 1 or 2, or shall contain the first byte of an audio frame if the PES stream type is 3 or 4. In addition, in - * the case of video, a presentation timestamp shall be present in the PES packet containing the first picture following the - * sequence header. In the case of audio, the presentation timestamp shall be present in the PES packet containing the first - * byte of the audio frame. In the PCR_PID the random_access_indicator may only be set to '1' in Transport Stream packet - * containing the PCR fields. - */ - public var random_access_indicator:int; //1bit - /** - * The elementary_stream_priority_indicator is a 1-bit field. It indicates, among - * packets with the same PID, the priority of the elementary stream data carried within the payload of this Transport Stream - * packet. A '1' indicates that the payload has a higher priority than the payloads of other Transport Stream packets. In the - * case of video, this field may be set to '1' only if the payload contains one or more bytes from an intra-coded slice. A - * value of '0' indicates that the payload has the same priority as all other packets which do not have this bit set to '1'. - */ - public var elementary_stream_priority_indicator:int; //1bit - /** - * The PCR_flag is a 1-bit flag. A value of '1' indicates that the adaptation_field contains a PCR field coded in - * two parts. A value of '0' indicates that the adaptation field does not contain any PCR field. - */ - public var PCR_flag:int; //1bit - /** - * The OPCR_flag is a 1-bit flag. A value of '1' indicates that the adaptation_field contains an OPCR field - * coded in two parts. A value of '0' indicates that the adaptation field does not contain any OPCR field. - */ - public var OPCR_flag:int; //1bit - /** - * The splicing_point_flag is a 1-bit flag. When set to '1', it indicates that a splice_countdown field - * shall be present in the associated adaptation field, specifying the occurrence of a splicing point. A value of '0' indicates - * that a splice_countdown field is not present in the adaptation field. - */ - public var splicing_point_flag:int; //1bit - /** - * The transport_private_data_flag is a 1-bit flag. A value of '1' indicates that the - * adaptation field contains one or more private_data bytes. A value of '0' indicates the adaptation field does not contain any - * private_data bytes. - */ - public var transport_private_data_flag:int; //1bit - /** - * The adaptation_field_extension_flag is a 1-bit field which when set to '1' indicates - * the presence of an adaptation field extension. A value of '0' indicates that an adaptation field extension is not present in - * the adaptation field. - */ - public var adaptation_field_extension_flag:int; //1bit - - // if PCR_flag, 6B - /** - * The program_clock_reference (PCR) is a - * 42-bit field coded in two parts. The first part, program_clock_reference_base, is a 33-bit field whose value is given by - * PCR_base(i), as given in equation 2-2. The second part, program_clock_reference_extension, is a 9-bit field whose value - * is given by PCR_ext(i), as given in equation 2-3. The PCR indicates the intended time of arrival of the byte containing - * the last bit of the program_clock_reference_base at the input of the system target decoder. - */ - public var program_clock_reference_base:Number; //33bits - /** - * 6bits reserved, must be '1' - */ - public var const1_value0:int; // 6bits - public var program_clock_reference_extension:int; //9bits - - // if OPCR_flag, 6B - /** - * The optional original - * program reference (OPCR) is a 42-bit field coded in two parts. These two parts, the base and the extension, are coded - * identically to the two corresponding parts of the PCR field. The presence of the OPCR is indicated by the OPCR_flag. - * The OPCR field shall be coded only in Transport Stream packets in which the PCR field is present. OPCRs are permitted - * in both single program and multiple program Transport Streams. - * - * OPCR assists in the reconstruction of a single program Transport Stream from another Transport Stream. When - * reconstructing the original single program Transport Stream, the OPCR may be copied to the PCR field. The resulting - * PCR value is valid only if the original single program Transport Stream is reconstructed exactly in its entirety. This - * would include at least any PSI and private data packets which were present in the original Transport Stream and would - * possibly require other private arrangements. It also means that the OPCR must be an identical copy of its associated PCR - * in the original single program Transport Stream. - */ - public var original_program_clock_reference_base:Number; //33bits - /** - * 6bits reserved, must be '1' - */ - public var const1_value2:int; // 6bits - public var original_program_clock_reference_extension:int; //9bits - - // if splicing_point_flag, 1B - /** - * The splice_countdown is an 8-bit field, representing a value which may be positive or negative. A - * positive value specifies the remaining number of Transport Stream packets, of the same PID, following the associated - * Transport Stream packet until a splicing point is reached. Duplicate Transport Stream packets and Transport Stream - * packets which only contain adaptation fields are excluded. The splicing point is located immediately after the last byte of - * the Transport Stream packet in which the associated splice_countdown field reaches zero. In the Transport Stream packet - * where the splice_countdown reaches zero, the last data byte of the Transport Stream packet payload shall be the last byte - * of a coded audio frame or a coded picture. In the case of video, the corresponding access unit may or may not be - * terminated by a sequence_end_code. Transport Stream packets with the same PID, which follow, may contain data from - * a different elementary stream of the same type. - * - * The payload of the next Transport Stream packet of the same PID (duplicate packets and packets without payload being - * excluded) shall commence with the first byte of a PES packet.In the case of audio, the PES packet payload shall - * commence with an access point. In the case of video, the PES packet payload shall commence with an access point, or - * with a sequence_end_code, followed by an access point. Thus, the previous coded audio frame or coded picture aligns - * with the packet boundary, or is padded to make this so. Subsequent to the splicing point, the countdown field may also - * be present. When the splice_countdown is a negative number whose value is minus n(-n), it indicates that the associated - * Transport Stream packet is the n-th packet following the splicing point (duplicate packets and packets without payload - * being excluded). - * - * For the purposes of this subclause, an access point is defined as follows: - * Video - The first byte of a video_sequence_header. - * Audio - The first byte of an audio frame. - */ - public var splice_countdown:int; //8bits - - // if transport_private_data_flag, 1+p[0] B - /** - * The transport_private_data_length is an 8-bit field specifying the number of - * private_data bytes immediately following the transport private_data_length field. The number of private_data bytes shall - * not be such that private data extends beyond the adaptation field. - */ - public var transport_private_data_length:int; //8bits - public var transport_private_data:ByteArray; //[transport_private_data_length]bytes - - // if adaptation_field_extension_flag, 2+x B - /** - * The adaptation_field_extension_length is an 8-bit field. It indicates the number of - * bytes of the extended adaptation field data immediately following this field, including reserved bytes if present. - */ - public var adaptation_field_extension_length:int; //8bits - /** - * This is a 1-bit field which when set to '1' indicates the presence of the ltw_offset - * field. - */ - public var ltw_flag:int; //1bit - /** - * This is a 1-bit field which when set to '1' indicates the presence of the piecewise_rate field. - */ - public var piecewise_rate_flag:int; //1bit - /** - * This is a 1-bit flag which when set to '1' indicates that the splice_type and DTS_next_AU fields - * are present. A value of '0' indicates that neither splice_type nor DTS_next_AU fields are present. This field shall not be - * set to '1' in Transport Stream packets in which the splicing_point_flag is not set to '1'. Once it is set to '1' in a Transport - * Stream packet in which the splice_countdown is positive, it shall be set to '1' in all the subsequent Transport Stream - * packets of the same PID that have the splicing_point_flag set to '1', until the packet in which the splice_countdown - * reaches zero (including this packet). When this flag is set, if the elementary stream carried in this PID is an audio stream, - * the splice_type field shall be set to '0000'. If the elementary stream carried in this PID is a video stream, it shall fulfil the - * constraints indicated by the splice_type value. - */ - public var seamless_splice_flag:int; //1bit - /** - * reserved 5bits, must be '1' - */ - public var const1_value1:int; //5bits - // if ltw_flag, 2B - /** - * (legal time window_valid_flag) - This is a 1-bit field which when set to '1' indicates that the value of the - * ltw_offset shall be valid. A value of '0' indicates that the value in the ltw_offset field is undefined. - */ - public var ltw_valid_flag:int; //1bit - /** - * (legal time window offset) - This is a 15-bit field, the value of which is defined only if the ltw_valid flag has - * a value of '1'. When defined, the legal time window offset is in units of (300/fs) seconds, where fs is the system clock - * frequency of the program that this PID belongs to, and fulfils: - * offset = t1(i) - t(i) - * ltw_offset = offset//1 - * where i is the index of the first byte of this Transport Stream packet, offset is the value encoded in this field, t(i) is the - * arrival time of byte i in the T-STD, and t1(i) is the upper bound in time of a time interval called the Legal Time Window - * which is associated with this Transport Stream packet. - */ - public var ltw_offset:int; //15bits - // if piecewise_rate_flag, 3B - //2bits reserved - /** - * The meaning of this 22-bit field is only defined when both the ltw_flag and the ltw_valid_flag are set - * to '1'. When defined, it is a positive integer specifying a hypothetical bitrate R which is used to define the end times of - * the Legal Time Windows of Transport Stream packets of the same PID that follow this packet but do not include the - * legal_time_window_offset field. - */ - public var piecewise_rate:int; //22bits - // if seamless_splice_flag, 5B - /** - * This is a 4-bit field. From the first occurrence of this field onwards, it shall have the same value in all the - * subsequent Transport Stream packets of the same PID in which it is present, until the packet in which the - * splice_countdown reaches zero (including this packet). If the elementary stream carried in that PID is an audio stream, - * this field shall have the value '0000'. If the elementary stream carried in that PID is a video stream, this field indicates the - * conditions that shall be respected by this elementary stream for splicing purposes. These conditions are defined as a - * function of profile, level and splice_type in Table 2-7 through Table 2-16. - */ - public var splice_type:int; //4bits - /** - * (decoding time stamp next access unit) - This is a 33-bit field, coded in three parts. In the case of - * continuous and periodic decoding through this splicing point it indicates the decoding time of the first access unit - * following the splicing point. This decoding time is expressed in the time base which is valid in the Transport Stream - * packet in which the splice_countdown reaches zero. From the first occurrence of this field onwards, it shall have the - * same value in all the subsequent Transport Stream packets of the same PID in which it is present, until the packet in - * which the splice_countdown reaches zero (including this packet). - */ - public var DTS_next_AU0:int; //3bits - public var marker_bit0:int; //1bit - public var DTS_next_AU1:int; //15bits - public var marker_bit1:int; //1bit - public var DTS_next_AU2:int; //15bits - public var marker_bit2:int; //1bit - // left bytes. - /** - * This is a fixed 8-bit value equal to '1111 1111' that can be inserted by the encoder. It is discarded by the - * decoder. - */ - public var nb_af_ext_reserved:int; - - // left bytes. - /** - * This is a fixed 8-bit value equal to '1111 1111' that can be inserted by the encoder. It is discarded by the - * decoder. - */ - public var nb_af_reserved:int; - - private var packet:SrsTsPacket; - - public function SrsTsAdaptationField(pkt:SrsTsPacket) - { - packet = pkt; - - adaption_field_length = 0; - discontinuity_indicator = 0; - random_access_indicator = 0; - elementary_stream_priority_indicator = 0; - PCR_flag = 0; - OPCR_flag = 0; - splicing_point_flag = 0; - transport_private_data_flag = 0; - adaptation_field_extension_flag = 0; - program_clock_reference_base = 0; - program_clock_reference_extension = 0; - original_program_clock_reference_base = 0; - original_program_clock_reference_extension = 0; - splice_countdown = 0; - transport_private_data_length = 0; - transport_private_data = null; - adaptation_field_extension_length = 0; - ltw_flag = 0; - piecewise_rate_flag = 0; - seamless_splice_flag = 0; - ltw_valid_flag = 0; - ltw_offset = 0; - piecewise_rate = 0; - splice_type = 0; - DTS_next_AU0 = 0; - marker_bit0 = 0; - DTS_next_AU1 = 0; - marker_bit1 = 0; - DTS_next_AU2 = 0; - marker_bit2 = 0; - nb_af_ext_reserved = 0; - nb_af_reserved = 0; - - const1_value0 = 0x3F; - const1_value1 = 0x1F; - const1_value2 = 0x3F; - } - - public function decode(stream:ByteArray):void - { - if (stream.bytesAvailable < 2) { - throw new Error("ts: demux af failed."); - } - adaption_field_length = stream.readUnsignedByte(); - - // When the adaptation_field_control value is '11', the value of the adaptation_field_length shall - // be in the range 0 to 182. - if (packet.adaption_field_control == SrsTsAdaptationFieldType.Both && adaption_field_length > 182) { - throw new Error("ts: demux af length failed, must in [0, 182], actual=" + adaption_field_length); - } - // When the adaptation_field_control value is '10', the value of the adaptation_field_length shall - // be 183. - if (packet.adaption_field_control == SrsTsAdaptationFieldType.AdaptionOnly && adaption_field_length != 183) { - throw new Error("ts: demux af length failed, must be 183, actual=" + adaption_field_length); - } - - // no adaptation field. - if (adaption_field_length == 0) { - //info("ts: demux af empty."); - return; - } - - // the adaptation field start at here. - var pos_af:uint = stream.position; - var tmpv:int = stream.readUnsignedByte(); - - discontinuity_indicator = (tmpv >> 7) & 0x01; - random_access_indicator = (tmpv >> 6) & 0x01; - elementary_stream_priority_indicator = (tmpv >> 5) & 0x01; - PCR_flag = (tmpv >> 4) & 0x01; - OPCR_flag = (tmpv >> 3) & 0x01; - splicing_point_flag = (tmpv >> 2) & 0x01; - transport_private_data_flag = (tmpv >> 1) & 0x01; - adaptation_field_extension_flag = tmpv & 0x01; - - if (PCR_flag) { - if (stream.bytesAvailable < 6) { - throw new Error("ts: demux af PCR_flag failed."); - } - - // @remark, for as, should never shift the Number object. - // @remark, use pcr base and ignore the extension - // @see https://github.com/ossrs/srs/issues/250#issuecomment-71349370 - // first 33bits is pcr base. - program_clock_reference_base = (stream.readUnsignedInt() << 1) & 0x1fffffe; - var ch:uint = stream.readUnsignedByte(); - program_clock_reference_base += (ch >> 7) & 0x01; - // next 6bits is the const values - const1_value0 = (ch >> 1) & 0x3f; - // last 9bits is the pcr ext. - program_clock_reference_extension = (ch << 8) & 0x1ff; - program_clock_reference_extension |= stream.readUnsignedByte(); - } - - if (OPCR_flag) { - if (stream.bytesAvailable < 6) { - throw new Error("ts: demux af OPCR_flag failed."); - } - // @remark, for as, should never shift the Number object. - // @remark, use pcr base and ignore the extension - // @see https://github.com/ossrs/srs/issues/250#issuecomment-71349370 - // first 33bits is pcr base. - original_program_clock_reference_base = (stream.readUnsignedInt() << 1) & 0x1fffffe; - ch = stream.readUnsignedByte(); - original_program_clock_reference_base += (ch >> 7) & 0x01; - // next 6bits is the const values - const1_value2 = (ch >> 1) & 0x3f; - // last 9bits is the pcr ext. - original_program_clock_reference_extension = (ch << 8) & 0x1ff; - original_program_clock_reference_extension |= stream.readUnsignedByte(); - } - - if (splicing_point_flag) { - if (stream.bytesAvailable < 1) { - throw new Error("ts: demux af splicing_point_flag failed."); - } - splice_countdown = stream.readUnsignedByte(); - } - - if (transport_private_data_flag) { - if (stream.bytesAvailable < 1) { - throw new Error("ts: demux af transport_private_data_flag failed."); - } - transport_private_data_length = stream.readUnsignedByte(); - - if (transport_private_data_length> 0) { - if (stream.bytesAvailable < transport_private_data_length) { - throw new Error("ts: demux af transport_private_data_flag failed."); - } - transport_private_data = new ByteArray(); - stream.readBytes(transport_private_data, 0, transport_private_data_length); - } - } - - if (adaptation_field_extension_flag) { - var pos_af_ext:uint = stream.position; - - if (stream.bytesAvailable < 2) { - throw new Error("ts: demux af adaptation_field_extension_flag failed."); - } - adaptation_field_extension_length = stream.readUnsignedByte(); - var ltwfv:int = stream.readUnsignedByte(); - - piecewise_rate_flag = (ltwfv >> 6) & 0x01; - seamless_splice_flag = (ltwfv >> 5) & 0x01; - ltw_flag = (ltwfv >> 7) & 0x01; - const1_value1 = ltwfv & 0x1F; - - if (ltw_flag) { - if (stream.bytesAvailable < 2) { - throw new Error("ts: demux af ltw_flag failed."); - } - ltw_offset = stream.readUnsignedShort(); - - ltw_valid_flag = (ltw_offset >> 15) &0x01; - ltw_offset &= 0x7FFF; - } - - if (piecewise_rate_flag) { - if (stream.bytesAvailable < 3) { - throw new Error("ts: demux af piecewise_rate_flag failed."); - } - // skip -1 to read 4B for 3B actually - stream.position -= 1; - piecewise_rate = (stream.readUnsignedInt() & 0x00ffffff); - - piecewise_rate &= 0x3FFFFF; - } - - if (seamless_splice_flag) { - if (stream.bytesAvailable < 5) { - throw new Error("ts: demux af seamless_splice_flag failed"); - } - marker_bit0 = stream.readUnsignedByte(); - DTS_next_AU1 = stream.readUnsignedShort(); - DTS_next_AU2 = stream.readUnsignedShort(); - - splice_type = (marker_bit0 >> 4) & 0x0F; - DTS_next_AU0 = (marker_bit0 >> 1) & 0x07; - marker_bit0 &= 0x01; - - marker_bit1 = DTS_next_AU1 & 0x01; - DTS_next_AU1 = (DTS_next_AU1 >> 1) & 0x7FFF; - - marker_bit2 = DTS_next_AU2 & 0x01; - DTS_next_AU2 = (DTS_next_AU2 >> 1) & 0x7FFF; - } - - nb_af_ext_reserved = adaptation_field_extension_length - (stream.position - pos_af_ext); - stream.position += nb_af_ext_reserved; - } - - nb_af_reserved = adaption_field_length - (stream.position - pos_af); - stream.position += nb_af_reserved; - } -}; - -/** - * 2.4.4.4 Table_id assignments, hls-mpeg-ts-iso13818-1.pdf, page 62 - * The table_id field identifies the contents of a Transport Stream PSI section as shown in Table 2-26. - */ -class SrsTsPsiId extends SrsEnum -{ - public function SrsTsPsiId(v:int) - { - super(v); - } - public static function parse(v:int):SrsTsPsiId - { - switch (v) { - case 0x00: return SrsTsPsiId.Pas; - case 0x01: return SrsTsPsiId.Cas; - case 0x02: return SrsTsPsiId.Pms; - case 0x03: return SrsTsPsiId.Ds; - case 0x04: return SrsTsPsiId.Sds; - case 0x05: return SrsTsPsiId.Ods; - case 0x06: return SrsTsPsiId.Iso138181Start; - case 0x37: return SrsTsPsiId.Iso138181End; - case 0x38: return SrsTsPsiId.Iso138186Start; - case 0x3F: return SrsTsPsiId.Iso138186End; - case 0x40: return SrsTsPsiId.UserStart; - case 0xFE: return SrsTsPsiId.UserEnd; - case 0xFF: return SrsTsPsiId.Forbidden; - default: case 0x00: return SrsTsPsiId.Forbidden; - } - } - - // program_association_section - public static const Pas:SrsTsPsiId = new SrsTsPsiId(0x00); - // conditional_access_section (CA_section) - public static const Cas:SrsTsPsiId = new SrsTsPsiId(0x01); - // TS_program_map_section - public static const Pms:SrsTsPsiId = new SrsTsPsiId(0x02); - // TS_description_section - public static const Ds:SrsTsPsiId = new SrsTsPsiId(0x03); - // ISO_IEC_14496_scene_description_section - public static const Sds:SrsTsPsiId = new SrsTsPsiId(0x04); - // ISO_IEC_14496_object_descriptor_section - public static const Ods:SrsTsPsiId = new SrsTsPsiId(0x05); - // ITU-T Rec. H.222.0 | ISO/IEC 13818-1 reserved - public static const Iso138181Start:SrsTsPsiId = new SrsTsPsiId(0x06); - public static const Iso138181End:SrsTsPsiId = new SrsTsPsiId(0x37); - // Defined in ISO/IEC 13818-6 - public static const Iso138186Start:SrsTsPsiId = new SrsTsPsiId(0x38); - public static const Iso138186End:SrsTsPsiId = new SrsTsPsiId(0x3F); - // User private - public static const UserStart:SrsTsPsiId = new SrsTsPsiId(0x40); - public static const UserEnd:SrsTsPsiId = new SrsTsPsiId(0xFE); - // forbidden - public static const Forbidden:SrsTsPsiId = new SrsTsPsiId(0xFF); -}; - -/** - * the payload of ts packet, can be PES or PSI payload. - */ -class SrsTsPayload -{ - protected var packet:SrsTsPacket; - - public function SrsTsPayload(p:SrsTsPacket) - { - packet = p; - } - - public function decode(stream:ByteArray):SrsTsMessage - { - throw new Error("not implements"); - } -}; - -/** - * the PES payload of ts packet. - * 2.4.3.6 PES packet, hls-mpeg-ts-iso13818-1.pdf, page 49 - */ -class SrsTsPayloadPES extends SrsTsPayload -{ - // 3B - /** - * The packet_start_code_prefix is a 24-bit code. Together with the stream_id that follows it - * constitutes a packet start code that identifies the beginning of a packet. The packet_start_code_prefix is the bit string - * '0000 0000 0000 0000 0000 0001' (0x000001). - */ - public var packet_start_code_prefix:int; //24bits - // 1B - /** - * In Program Streams, the stream_id specifies the type and number of the elementary stream as defined by the - * stream_id Table 2-18. In Transport Streams, the stream_id may be set to any valid value which correctly describes the - * elementary stream type as defined in Table 2-18. In Transport Streams, the elementary stream type is specified in the - * Program Specific Information as specified in 2.4.4. - */ - // @see SrsTsPESStreamId, value can be SrsTsPESStreamIdAudioCommon or SrsTsPESStreamIdVideoCommon. - public var stream_id:int; //8bits - // 2B - /** - * A 16-bit field specifying the number of bytes in the PES packet following the last byte of the - * field. A value of 0 indicates that the PES packet length is neither specified nor bounded and is allowed only in - * PES packets whose payload consists of bytes from a video elementary stream contained in Transport Stream packets. - */ - public var PES_packet_length:int; //16bits - - // 1B - /** - * 2bits const '10' - */ - public var const2bits:int; //2bits - /** - * The 2-bit PES_scrambling_control field indicates the scrambling mode of the PES packet - * payload. When scrambling is performed at the PES level, the PES packet header, including the optional fields when - * present, shall not be scrambled (see Table 2-19). - */ - public var PES_scrambling_control:int; //2bits - /** - * This is a 1-bit field indicating the priority of the payload in this PES packet. A '1' indicates a higher - * priority of the payload of the PES packet payload than a PES packet payload with this field set to '0'. A multiplexor can - * use the PES_priority bit to prioritize its data within an elementary stream. This field shall not be changed by the transport - * mechanism. - */ - public var PES_priority:int; //1bit - /** - * This is a 1-bit flag. When set to a value of '1' it indicates that the PES packet header is - * immediately followed by the video start code or audio syncword indicated in the data_stream_alignment_descriptor - * in 2.6.10 if this descriptor is present. If set to a value of '1' and the descriptor is not present, alignment as indicated in - * alignment_type '01' in Table 2-47 and Table 2-48 is required. When set to a value of '0' it is not defined whether any such - * alignment occurs or not. - */ - public var data_alignment_indicator:int; //1bit - /** - * This is a 1-bit field. When set to '1' it indicates that the material of the associated PES packet payload is - * protected by copyright. When set to '0' it is not defined whether the material is protected by copyright. A copyright - * descriptor described in 2.6.24 is associated with the elementary stream which contains this PES packet and the copyright - * flag is set to '1' if the descriptor applies to the material contained in this PES packet - */ - public var copyright:int; //1bit - /** - * This is a 1-bit field. When set to '1' the contents of the associated PES packet payload is an original. - * When set to '0' it indicates that the contents of the associated PES packet payload is a copy. - */ - public var original_or_copy:int; //1bit - - // 1B - /** - * This is a 2-bit field. When the PTS_DTS_flags field is set to '10', the PTS fields shall be present in - * the PES packet header. When the PTS_DTS_flags field is set to '11', both the PTS fields and DTS fields shall be present - * in the PES packet header. When the PTS_DTS_flags field is set to '00' no PTS or DTS fields shall be present in the PES - * packet header. The value '01' is forbidden. - */ - public var PTS_DTS_flags:int; //2bits - /** - * A 1-bit flag, which when set to '1' indicates that ESCR base and extension fields are present in the PES - * packet header. When set to '0' it indicates that no ESCR fields are present. - */ - public var ESCR_flag:int; //1bit - /** - * A 1-bit flag, which when set to '1' indicates that the ES_rate field is present in the PES packet header. - * When set to '0' it indicates that no ES_rate field is present. - */ - public var ES_rate_flag:int; //1bit - /** - * A 1-bit flag, which when set to '1' it indicates the presence of an 8-bit trick mode field. When - * set to '0' it indicates that this field is not present. - */ - public var DSM_trick_mode_flag:int; //1bit - /** - * A 1-bit flag, which when set to '1' indicates the presence of the additional_copy_info field. - * When set to '0' it indicates that this field is not present. - */ - public var additional_copy_info_flag:int; //1bit - /** - * A 1-bit flag, which when set to '1' indicates that a CRC field is present in the PES packet. When set to - * '0' it indicates that this field is not present. - */ - public var PES_CRC_flag:int; //1bit - /** - * A 1-bit flag, which when set to '1' indicates that an extension field exists in this PES packet - * header. When set to '0' it indicates that this field is not present. - */ - public var PES_extension_flag:int; //1bit - - // 1B - /** - * An 8-bit field specifying the total number of bytes occupied by the optional fields and any - * stuffing bytes contained in this PES packet header. The presence of optional fields is indicated in the byte that precedes - * the PES_header_data_length field. - */ - public var PES_header_data_length:int; //8bits - - // 5B - /** - * Presentation times shall be related to decoding times as follows: The PTS is a 33-bit - * number coded in three separate fields. It indicates the time of presentation, tp n (k), in the system target decoder of a - * presentation unit k of elementary stream n. The value of PTS is specified in units of the period of the system clock - * frequency divided by 300 (yielding 90 kHz). The presentation time is derived from the PTS according to equation 2-11 - * below. Refer to 2.7.4 for constraints on the frequency of coding presentation timestamps. - */ - // ===========1B - // 4bits const - // 3bits PTS [32..30] - // 1bit const '1' - // ===========2B - // 15bits PTS [29..15] - // 1bit const '1' - // ===========2B - // 15bits PTS [14..0] - // 1bit const '1' - public var pts:Number; // 33bits - - // 5B - /** - * The DTS is a 33-bit number coded in three separate fields. It indicates the decoding time, - * td n (j), in the system target decoder of an access unit j of elementary stream n. The value of DTS is specified in units of - * the period of the system clock frequency divided by 300 (yielding 90 kHz). - */ - // ===========1B - // 4bits const - // 3bits DTS [32..30] - // 1bit const '1' - // ===========2B - // 15bits DTS [29..15] - // 1bit const '1' - // ===========2B - // 15bits DTS [14..0] - // 1bit const '1' - public var dts:Number; // 33bits - - // 6B - /** - * The elementary stream clock reference is a 42-bit field coded in two parts. The first - * part, ESCR_base, is a 33-bit field whose value is given by ESCR_base(i), as given in equation 2-14. The second part, - * ESCR_ext, is a 9-bit field whose value is given by ESCR_ext(i), as given in equation 2-15. The ESCR field indicates the - * intended time of arrival of the byte containing the last bit of the ESCR_base at the input of the PES-STD for PES streams - * (refer to 2.5.2.4). - */ - // 2bits reserved - // 3bits ESCR_base[32..30] - // 1bit const '1' - // 15bits ESCR_base[29..15] - // 1bit const '1' - // 15bits ESCR_base[14..0] - // 1bit const '1' - // 9bits ESCR_extension - // 1bit const '1' - public var ESCR_base:Number; //33bits - public var ESCR_extension:int; //9bits - - // 3B - /** - * The ES_rate field is a 22-bit unsigned integer specifying the rate at which the - * system target decoder receives bytes of the PES packet in the case of a PES stream. The ES_rate is valid in the PES - * packet in which it is included and in subsequent PES packets of the same PES stream until a new ES_rate field is - * encountered. The value of the ES_rate is measured in units of 50 bytes/second. The value 0 is forbidden. The value of the - * ES_rate is used to define the time of arrival of bytes at the input of a P-STD for PES streams defined in 2.5.2.4. The - * value encoded in the ES_rate field may vary from PES_packet to PES_packet. - */ - // 1bit const '1' - // 22bits ES_rate - // 1bit const '1' - public var ES_rate:int; //22bits - - // 1B - /** - * A 3-bit field that indicates which trick mode is applied to the associated video stream. In cases of - * other types of elementary streams, the meanings of this field and those defined by the following five bits are undefined. - * For the definition of trick_mode status, refer to the trick mode section of 2.4.2.3. - */ - public var trick_mode_control:int; //3bits - public var trick_mode_value:int; //5bits - - // 1B - // 1bit const '1' - /** - * This 7-bit field contains private data relating to copyright information. - */ - public var additional_copy_info:int; //7bits - - // 2B - /** - * The previous_PES_packet_CRC is a 16-bit field that contains the CRC value that yields - * a zero output of the 16 registers in the decoder similar to the one defined in Annex A, - */ - public var previous_PES_packet_CRC:int; //16bits - - // 1B - /** - * A 1-bit flag which when set to '1' indicates that the PES packet header contains private data. - * When set to a value of '0' it indicates that private data is not present in the PES header. - */ - public var PES_private_data_flag:int; //1bit - /** - * A 1-bit flag which when set to '1' indicates that an ISO/IEC 11172-1 pack header or a - * Program Stream pack header is stored in this PES packet header. If this field is in a PES packet that is contained in a - * Program Stream, then this field shall be set to '0'. In a Transport Stream, when set to the value '0' it indicates that no pack - * header is present in the PES header. - */ - public var pack_header_field_flag:int; //1bit - /** - * A 1-bit flag which when set to '1' indicates that the - * program_packet_sequence_counter, MPEG1_MPEG2_identifier, and original_stuff_length fields are present in this - * PES packet. When set to a value of '0' it indicates that these fields are not present in the PES header. - */ - public var program_packet_sequence_counter_flag:int; //1bit - /** - * A 1-bit flag which when set to '1' indicates that the P-STD_buffer_scale and P-STD_buffer_size - * are present in the PES packet header. When set to a value of '0' it indicates that these fields are not present in the - * PES header. - */ - public var P_STD_buffer_flag:int; //1bit - /** - * reverved value, must be '1' - */ - public var const1_value0:int; //3bits - /** - * A 1-bit field which when set to '1' indicates the presence of the PES_extension_field_length - * field and associated fields. When set to a value of '0' this indicates that the PES_extension_field_length field and any - * associated fields are not present. - */ - public var PES_extension_flag_2:int; //1bit - - // 16B - /** - * This is a 16-byte field which contains private data. This data, combined with the fields before and - * after, shall not emulate the packet_start_code_prefix (0x000001). - */ - public var PES_private_data:ByteArray; //128bits - - // (1+x)B - /** - * This is an 8-bit field which indicates the length, in bytes, of the pack_header_field(). - */ - public var pack_field_length:int; //8bits - public var pack_field:ByteArray; //[pack_field_length] bytes - - // 2B - // 1bit const '1' - /** - * The program_packet_sequence_counter field is a 7-bit field. It is an optional - * counter that increments with each successive PES packet from a Program Stream or from an ISO/IEC 11172-1 Stream or - * the PES packets associated with a single program definition in a Transport Stream, providing functionality similar to a - * continuity counter (refer to 2.4.3.2). This allows an application to retrieve the original PES packet sequence of a Program - * Stream or the original packet sequence of the original ISO/IEC 11172-1 stream. The counter will wrap around to 0 after - * its maximum value. Repetition of PES packets shall not occur. Consequently, no two consecutive PES packets in the - * program multiplex shall have identical program_packet_sequence_counter values. - */ - public var program_packet_sequence_counter:int; //7bits - // 1bit const '1' - /** - * A 1-bit flag which when set to '1' indicates that this PES packet carries information from - * an ISO/IEC 11172-1 stream. When set to '0' it indicates that this PES packet carries information from a Program Stream. - */ - public var MPEG1_MPEG2_identifier:int; //1bit - /** - * This 6-bit field specifies the number of stuffing bytes used in the original ITU-T - * Rec. H.222.0 | ISO/IEC 13818-1 PES packet header or in the original ISO/IEC 11172-1 packet header. - */ - public var original_stuff_length:int; //6bits - - // 2B - // 2bits const '01' - /** - * The P-STD_buffer_scale is a 1-bit field, the meaning of which is only defined if this PES packet - * is contained in a Program Stream. It indicates the scaling factor used to interpret the subsequent P-STD_buffer_size field. - * If the preceding stream_id indicates an audio stream, P-STD_buffer_scale shall have the value '0'. If the preceding - * stream_id indicates a video stream, P-STD_buffer_scale shall have the value '1'. For all other stream types, the value - * may be either '1' or '0'. - */ - public var P_STD_buffer_scale:int; //1bit - /** - * The P-STD_buffer_size is a 13-bit unsigned integer, the meaning of which is only defined if this - * PES packet is contained in a Program Stream. It defines the size of the input buffer, BS n , in the P-STD. If - * P-STD_buffer_scale has the value '0', then the P-STD_buffer_size measures the buffer size in units of 128 bytes. If - * P-STD_buffer_scale has the value '1', then the P-STD_buffer_size measures the buffer size in units of 1024 bytes. - */ - public var P_STD_buffer_size:int; //13bits - - // (1+x)B - // 1bit const '1' - /** - * This is a 7-bit field which specifies the length, in bytes, of the data following this field in - * the PES extension field up to and including any reserved bytes. - */ - public var PES_extension_field_length:int; //7bits - public var PES_extension_field:ByteArray; //[PES_extension_field_length] bytes - - // NB - /** - * This is a fixed 8-bit value equal to '1111 1111' that can be inserted by the encoder, for example to meet - * the requirements of the channel. It is discarded by the decoder. No more than 32 stuffing bytes shall be present in one - * PES packet header. - */ - public var nb_stuffings:int; - - // NB - /** - * PES_packet_data_bytes shall be contiguous bytes of data from the elementary stream - * indicated by the packet’s stream_id or PID. When the elementary stream data conforms to ITU-T - * Rec. H.262 | ISO/IEC 13818-2 or ISO/IEC 13818-3, the PES_packet_data_bytes shall be byte aligned to the bytes of this - * Recommendation | International Standard. The byte-order of the elementary stream shall be preserved. The number of - * PES_packet_data_bytes, N, is specified by the PES_packet_length field. N shall be equal to the value indicated in the - * PES_packet_length minus the number of bytes between the last byte of the PES_packet_length field and the first - * PES_packet_data_byte. - * - * In the case of a private_stream_1, private_stream_2, ECM_stream, or EMM_stream, the contents of the - * PES_packet_data_byte field are user definable and will not be specified by ITU-T | ISO/IEC in the future. - */ - public var nb_bytes:int; - - // NB - /** - * This is a fixed 8-bit value equal to '1111 1111'. It is discarded by the decoder. - */ - public var nb_paddings:int; - - private var _log:ILogger = new TraceLogger("HLS"); - - public function SrsTsPayloadPES(p:SrsTsPacket) - { - super(p); - - PES_private_data = null; - pack_field = null; - PES_extension_field = null; - nb_stuffings = 0; - nb_bytes = 0; - nb_paddings = 0; - const2bits = 0x02; - const1_value0 = 0x07; - } - - override public function decode(stream:ByteArray):SrsTsMessage - { - // find the channel from chunk. - var channel:SrsTsChannel = packet.context.getChannel(packet.pid); - if (!channel) { - throw new Error("ts: demux PES no channel, pid=" + packet.pid); - } - - // init msg. - var msg:SrsTsMessage = channel.msg; - if (!msg) { - msg = new SrsTsMessage(channel, packet); - channel.msg = msg; - } - - // we must cache the fresh state of msg, - // for the PES_packet_length is 0, the first payload_unit_start_indicator always 1, - // so should check for the fresh and not completed it. - var is_fresh_msg:Boolean = msg.fresh(); - - // check when fresh, the payload_unit_start_indicator - // should be 1 for the fresh msg. - if (is_fresh_msg && !packet.payload_unit_start_indicator) { - _log.error("ts: PES fresh packet length={0}, us={1}, cc={2}", - msg.PES_packet_length, packet.payload_unit_start_indicator, packet.continuity_counter); - throw new Error("ts: PES fresh packet invalid."); - } - - // check when not fresh and PES_packet_length>0, - // the payload_unit_start_indicator should never be 1 when not completed. - if (!is_fresh_msg && msg.PES_packet_length > 0 - && !msg.completed(packet.payload_unit_start_indicator) - && packet.payload_unit_start_indicator - ) { - _log.error("ts: PES packet length={0}, us={1}, cc={2}", - msg.PES_packet_length, packet.payload_unit_start_indicator, packet.continuity_counter); - throw new Error("ts: PES packet length invalid."); - - // reparse current msg. - stream.position = 0; - channel.msg = null; - return null; - } - - // check the continuity counter - if (!is_fresh_msg) { - // late-incoming or duplicated continuity, drop message. - // @remark check overflow, the counter plus 1 should greater when invalid. - if (msg.continuity_counter >= packet.continuity_counter - && ((msg.continuity_counter + 1) & 0x0f) > packet.continuity_counter - ) { - _log.warn("ts: drop PES {0}B for duplicated cc={1}", - stream.length, msg.continuity_counter); - stream.position = 0; - return null; - } - - // when got partially message, the continous count must be continuous, or drop it. - if (((msg.continuity_counter + 1) & 0x0f) != packet.continuity_counter) { - _log.error("ts: continuity must be continous, msg={0}, packet={1}", - msg.continuity_counter, packet.continuity_counter); - throw new Error("ts: continuity must be continous."); - - // reparse current msg. - stream.position = 0; - channel.msg = null; - return null; - } - } - msg.continuity_counter = packet.continuity_counter; - - // for the PES_packet_length(0), reap when completed. - if (!is_fresh_msg && msg.completed(packet.payload_unit_start_indicator)) { - // reap previous PES packet. - channel.msg = null; - - // reparse current msg. - stream.position = 0; - return msg; - } - - // contious packet, append bytes for unit start is 0 - if (!packet.payload_unit_start_indicator) { - nb_bytes = msg.dump(stream); - } - - // when unit start, parse the fresh msg. - if (packet.payload_unit_start_indicator) { - // 6B fixed header. - if (stream.bytesAvailable < 6) { - throw new Error("ts: demux PSE failed."); - } - // 3B - stream.position -= 1; - packet_start_code_prefix = (stream.readUnsignedInt()) & 0x00ffffff; - // 1B - stream_id = stream.readUnsignedByte(); - // 2B - PES_packet_length = stream.readUnsignedShort(); - - // check the packet start prefix. - packet_start_code_prefix &= 0xFFFFFF; - if (packet_start_code_prefix != 0x01) { - _log.error("ts: demux PES start code failed, expect=0x01, actual={0}", - packet_start_code_prefix); - throw new Error("ts: demux PES start code failed."); - } - var pos_packet:uint = stream.position; - - // @remark the sid indicates the elementary stream format. - // the SrsTsPESStreamIdAudio and SrsTsPESStreamIdVideo is start by 0b110 or 0b1110 - var sid:SrsTsPESStreamId = SrsTsPESStreamId.parse(stream_id); - msg.sid = sid; - - if (sid.notEquals(SrsTsPESStreamId.ProgramStreamMap) - && sid.notEquals(SrsTsPESStreamId.PaddingStream) - && sid.notEquals(SrsTsPESStreamId.PrivateStream2) - && sid.notEquals(SrsTsPESStreamId.EcmStream) - && sid.notEquals(SrsTsPESStreamId.EmmStream) - && sid.notEquals(SrsTsPESStreamId.ProgramStreamDirectory) - && sid.notEquals(SrsTsPESStreamId.DsmccStream) - && sid.notEquals(SrsTsPESStreamId.H2221TypeE) - ) { - // 3B flags. - if (stream.bytesAvailable < 3) { - throw new Error("ts: demux PSE flags failed."); - } - // 1B - var oocv:int = stream.readUnsignedByte(); - // 1B - var pefv:int = stream.readUnsignedByte(); - // 1B - PES_header_data_length = stream.readUnsignedByte(); - // position of header start. - var pos_header:uint = stream.position; - - const2bits = (oocv >> 6) & 0x03; - PES_scrambling_control = (oocv >> 4) & 0x03; - PES_priority = (oocv >> 3) & 0x01; - data_alignment_indicator = (oocv >> 2) & 0x01; - copyright = (oocv >> 1) & 0x01; - original_or_copy = oocv & 0x01; - - PTS_DTS_flags = (pefv >> 6) & 0x03; - ESCR_flag = (pefv >> 5) & 0x01; - ES_rate_flag = (pefv >> 4) & 0x01; - DSM_trick_mode_flag = (pefv >> 3) & 0x01; - additional_copy_info_flag = (pefv >> 2) & 0x01; - PES_CRC_flag = (pefv >> 1) & 0x01; - PES_extension_flag = pefv & 0x01; - - // check required together. - var nb_required:int = 0; - nb_required += (PTS_DTS_flags == 0x2)? 5:0; - nb_required += (PTS_DTS_flags == 0x3)? 10:0; - nb_required += ESCR_flag? 6:0; - nb_required += ES_rate_flag? 3:0; - nb_required += DSM_trick_mode_flag? 1:0; - nb_required += additional_copy_info_flag? 1:0; - nb_required += PES_CRC_flag? 2:0; - nb_required += PES_extension_flag? 1:0; - if (stream.bytesAvailable < nb_required) { - throw new Error("ts: demux PSE payload failed."); - } - - // 5B - if (PTS_DTS_flags == 0x2) { - pts = decode_33bits_dts_pts(stream); - dts = pts; - - // update the dts and pts of message. - msg.dts = dts; - msg.pts = pts; - } - - // 10B - if (PTS_DTS_flags == 0x3) { - pts = decode_33bits_dts_pts(stream); - dts = decode_33bits_dts_pts(stream); - - // check sync, the diff of dts and pts should never greater than 1s. - if (dts - pts > 90000 || pts - dts > 90000) { - _log.warn("ts: sync dts={0}, pts={1}", dts, pts); - } - - // update the dts and pts of message. - msg.dts = dts; - msg.pts = pts; - } - - // 6B - if (ESCR_flag) { - ESCR_extension = 0; - ESCR_base = 0; - - stream.position += 6; - _log.warn("ts: demux PES, ignore the escr."); - } - - // 3B - if (ES_rate_flag) { - // skip -1 to read 4bytes for the ES_rate is 3B. - stream.position -= 1; - ES_rate = (stream.readUnsignedInt() & 0x00ffffff); - - ES_rate = ES_rate >> 1; - ES_rate &= 0x3FFFFF; - } - - // 1B - if (DSM_trick_mode_flag) { - trick_mode_control = stream.readUnsignedByte(); - - trick_mode_value = trick_mode_control & 0x1f; - trick_mode_control = (trick_mode_control >> 5) & 0x03; - } - - // 1B - if (additional_copy_info_flag) { - additional_copy_info = stream.readUnsignedByte(); - - additional_copy_info &= 0x7f; - } - - // 2B - if (PES_CRC_flag) { - previous_PES_packet_CRC = stream.readUnsignedShort(); - } - - // 1B - if (PES_extension_flag) { - var efv:int = stream.readUnsignedByte(); - - PES_private_data_flag = (efv >> 7) & 0x01; - pack_header_field_flag = (efv >> 6) & 0x01; - program_packet_sequence_counter_flag = (efv >> 5) & 0x01; - P_STD_buffer_flag = (efv >> 4) & 0x01; - const1_value0 = (efv >> 1) & 0x07; - PES_extension_flag_2 = efv & 0x01; - - nb_required = 0; - nb_required += PES_private_data_flag? 16:0; - nb_required += pack_header_field_flag? 1:0; // 1+x bytes. - nb_required += program_packet_sequence_counter_flag? 2:0; - nb_required += P_STD_buffer_flag? 2:0; - nb_required += PES_extension_flag_2? 1:0; // 1+x bytes. - if (stream.bytesAvailable < nb_required) { - throw new Error("ts: demux PSE ext payload failed."); - } - - // 16B - if (PES_private_data_flag) { - PES_private_data = new ByteArray(); - stream.readBytes(PES_private_data, 0, 16); - } - - // (1+x)B - if (pack_header_field_flag) { - pack_field_length = stream.readUnsignedByte(); - if (pack_field_length > 0) { - // the adjust required bytes. - nb_required = nb_required - 16 - 1 + pack_field_length; - if (stream.bytesAvailable < nb_required) { - throw new Error("ts: demux PSE ext pack failed."); - } - pack_field = new ByteArray(); - stream.readBytes(pack_field, 0, pack_field_length); - } - } - - // 2B - if (program_packet_sequence_counter_flag) { - program_packet_sequence_counter = stream.readUnsignedByte(); - program_packet_sequence_counter &= 0x7f; - - original_stuff_length = stream.readUnsignedByte(); - MPEG1_MPEG2_identifier = (original_stuff_length >> 6) & 0x01; - original_stuff_length &= 0x3f; - } - - // 2B - if (P_STD_buffer_flag) { - P_STD_buffer_size = stream.readUnsignedShort(); - - // '01' - //int8_t const2bits = (P_STD_buffer_scale >>14) & 0x03; - - P_STD_buffer_scale = (P_STD_buffer_scale >>13) & 0x01; - P_STD_buffer_size &= 0x1FFF; - } - - // (1+x)B - if (PES_extension_flag_2) { - PES_extension_field_length = stream.readUnsignedByte(); - PES_extension_field_length &= 0x07; - - if (PES_extension_field_length > 0) { - if (stream.bytesAvailable < PES_extension_field_length) { - throw new Error("ts: demux PSE ext field failed."); - } - PES_extension_field = new ByteArray(); - stream.readBytes(PES_extension_field, 0, PES_extension_field_length); - } - } - } - - // stuffing_byte - nb_stuffings = PES_header_data_length - (stream.position - pos_header); - if (nb_stuffings > 0) { - if (stream.bytesAvailable < nb_stuffings) { - throw new Error("ts: demux PSE stuffings failed."); - } - stream.position += nb_stuffings; - } - - // PES_packet_data_byte, page58. - // the packet size contains the header size. - // The number of PES_packet_data_bytes, N, is specified by the - // PES_packet_length field. N shall be equal to the value - // indicated in the PES_packet_length minus the number of bytes - // between the last byte of the PES_packet_length field and the - // first PES_packet_data_byte. - /** - * when actual packet length > 0xffff(65535), - * which exceed the max u_int16_t packet length, - * use 0 packet length, the next unit start indicates the end of packet. - */ - if (PES_packet_length > 0) { - var nb_packet:uint = PES_packet_length - (stream.position - pos_packet); - msg.PES_packet_length = (uint)(Math.max(0, nb_packet)); - } - - // xB - nb_bytes = msg.dump(stream); - } else if (sid.equals(SrsTsPESStreamId.ProgramStreamMap) - || sid.equals(SrsTsPESStreamId.PrivateStream2) - || sid.equals(SrsTsPESStreamId.EcmStream) - || sid.equals(SrsTsPESStreamId.EmmStream) - || sid.equals(SrsTsPESStreamId.ProgramStreamDirectory) - || sid.equals(SrsTsPESStreamId.DsmccStream) - || sid.equals(SrsTsPESStreamId.H2221TypeE) - ) { - // for (i = 0; i < PES_packet_length; i++) { - // PES_packet_data_byte - // } - - // xB - nb_bytes = msg.dump(stream); - } else if (sid.equals(SrsTsPESStreamId.PaddingStream)) { - // for (i = 0; i < PES_packet_length; i++) { - // padding_byte - // } - nb_paddings = stream.length - stream.position; - stream.position = stream.length; - //info("ts: drop %dB padding bytes", nb_paddings); - } else { - var nb_drop:int = stream.length - stream.position; - stream.position = stream.length; - _log.warn("ts: drop the pes packet {0}B for stream_id={1}", nb_drop, stream_id); - } - } - - // when fresh and the PES_packet_length is 0, - // the payload_unit_start_indicator always be 1, - // the message should never EOF for the first packet. - if (is_fresh_msg && msg.PES_packet_length == 0) { - return null; - } - - // check msg, reap when completed. - if (msg.completed(packet.payload_unit_start_indicator)) { - channel.msg = null; - //info("ts: reap msg for completed."); - return msg; - } - - return null; - } - - private function decode_33bits_dts_pts(stream:ByteArray):Number - { - if (stream.bytesAvailable < 5) { - throw new Error("ts: demux PSE dts/pts failed"); - } - - // decode the 33bits schema. - // ===========1B - // 4bits const maybe '0001', '0010' or '0011'. - // 3bits DTS/PTS [32..30] - // 1bit const '1' - var dts_pts_30_32:uint = stream.readUnsignedByte(); - if ((dts_pts_30_32 & 0x01) != 0x01) { - throw new Error("ts: demux PSE dts/pts 30-32 failed."); - } - // @remark, we donot check the high 4bits, maybe '0001', '0010' or '0011'. - // so we just ensure the high 4bits is not 0x00. - if (((dts_pts_30_32 >> 4) & 0x0f) == 0x00) { - throw new Error("ts: demux PSE dts/pts 30-32 failed."); - } - dts_pts_30_32 = (dts_pts_30_32 >> 1) & 0x07; - - // ===========2B - // 15bits DTS/PTS [29..15] - // 1bit const '1' - var dts_pts_15_29:uint = stream.readUnsignedShort(); - if ((dts_pts_15_29 & 0x01) != 0x01) { - throw new Error("ts: demux PSE dts/pts 15-29 failed."); - } - dts_pts_15_29 = (dts_pts_15_29 >> 1) & 0x7fff; - - // ===========2B - // 15bits DTS/PTS [14..0] - // 1bit const '1' - var dts_pts_0_14:uint = stream.readUnsignedShort(); - if ((dts_pts_0_14 & 0x01) != 0x01) { - throw new Error("ts: demux PSE dts/pts 0-14 failed."); - } - dts_pts_0_14 = (dts_pts_0_14 >> 1) & 0x7fff; - - var v:uint = 0x00; - v += (dts_pts_30_32 << 30) & 0x1c0000000; - v += (dts_pts_15_29 << 15) & 0x3fff8000; - v += dts_pts_0_14 & 0x7fff; - return v; - } -}; - -/** - * the PSI payload of ts packet. - * 2.4.4 Program specific information, hls-mpeg-ts-iso13818-1.pdf, page 59 - */ -class SrsTsPayloadPSI extends SrsTsPayload -{ - // 1B - /** - * This is an 8-bit field whose value shall be the number of bytes, immediately following the pointer_field - * until the first byte of the first section that is present in the payload of the Transport Stream packet (so a value of 0x00 in - * the pointer_field indicates that the section starts immediately after the pointer_field). When at least one section begins in - * a given Transport Stream packet, then the payload_unit_start_indicator (refer to 2.4.3.2) shall be set to 1 and the first - * byte of the payload of that Transport Stream packet shall contain the pointer. When no section begins in a given - * Transport Stream packet, then the payload_unit_start_indicator shall be set to 0 and no pointer shall be sent in the - * payload of that packet. - */ - public var pointer_field:int; - // 1B - /** - * This is an 8-bit field, which shall be set to 0x00 as shown in Table 2-26. - */ - public var table_id:SrsTsPsiId; //8bits - - // 2B - /** - * The section_syntax_indicator is a 1-bit field which shall be set to '1'. - */ - public var section_syntax_indicator:int; //1bit - /** - * const value, must be '0' - */ - public var const0_value:int; //1bit - /** - * reverved value, must be '1' - */ - public var const1_value:int; //2bits - /** - * This is a 12-bit field, the first two bits of which shall be '00'. The remaining 10 bits specify the number - * of bytes of the section, starting immediately following the section_length field, and including the CRC. The value in this - * field shall not exceed 1021 (0x3FD). - */ - public var section_length:int; //12bits - - // the specified psi info, for example, PAT fields. - // 4B - /** - * This is a 32-bit field that contains the CRC value that gives a zero output of the registers in the decoder - * defined in Annex A after processing the entire section. - * @remark crc32(bytes without pointer field, before crc32 field) - */ - public var CRC_32:int; //32bits - - public function SrsTsPayloadPSI(p:SrsTsPacket) - { - super(p); - - pointer_field = 0; - const0_value = 0; - const1_value = 3; - CRC_32 = 0; - } - - override public function decode(stream:ByteArray):SrsTsMessage - { - /** - * When the payload of the Transport Stream packet contains PSI data, the payload_unit_start_indicator has the following - * significance: if the Transport Stream packet carries the first byte of a PSI section, the payload_unit_start_indicator value - * shall be '1', indicating that the first byte of the payload of this Transport Stream packet carries the pointer_field. If the - * Transport Stream packet does not carry the first byte of a PSI section, the payload_unit_start_indicator value shall be '0', - * indicating that there is no pointer_field in the payload. Refer to 2.4.4.1 and 2.4.4.2. This also applies to private streams of - * stream_type 5 (refer to Table 2-29). - */ - if (packet.payload_unit_start_indicator) { - if (stream.bytesAvailable < 1) { - throw new Error("ts: demux PSI failed."); - } - pointer_field = stream.readUnsignedByte(); - } - - // to calc the crc32 - var pat_pos:uint = stream.position; - - // atleast 3B for all psi. - if (stream.bytesAvailable < 3) { - throw new Error("ts: demux PSI failed."); - } - // 1B - table_id = SrsTsPsiId.parse(stream.readUnsignedByte()); - - // 2B - var slv:int = stream.readUnsignedShort(); - - section_syntax_indicator = (slv >> 15) & 0x01; - const0_value = (slv >> 14) & 0x01; - const1_value = (slv >> 12) & 0x03; - section_length = slv & 0x0FFF; - - // no section, ignore. - if (section_length == 0) { - // "ts: demux PAT ignore empty section" - return null; - } - - if (stream.bytesAvailable < section_length) { - throw new Error("ts: demux PAT section failed."); - } - - // call the virtual method of actual PSI. - psi_decode(stream); - - // 4B - if (stream.bytesAvailable < 4) { - throw new Error("ts: demux PSI crc32 failed."); - } - CRC_32 = stream.readUnsignedInt(); - - // verify crc32. - var pos:uint = stream.position; - - var crc32Bytes:ByteArray = new ByteArray(); - stream.position = pat_pos; - stream.readBytes(crc32Bytes, 0, pos - pat_pos - 4); - stream.position = pos; - - var expetCrc32:int = (int)(SrsUtils.srs_crc32(crc32Bytes)); - if (expetCrc32 != CRC_32) { - throw new Error("ts: verify PSI crc32 failed."); - } - stream.position = pos; - - // consume left stuffings - // TODO: FIXME: all stuffing must be 0xff. - stream.position = stream.length; - - return null; - } - - protected function psi_decode(stream:ByteArray):void - { - throw new Error("not implements"); - } -}; - -/** - * the program of PAT of PSI ts packet. - */ -class SrsTsPayloadPATProgram -{ - // 4B - /** - * Program_number is a 16-bit field. It specifies the program to which the program_map_PID is - * applicable. When set to 0x0000, then the following PID reference shall be the network PID. For all other cases the value - * of this field is user defined. This field shall not take any single value more than once within one version of the Program - * Association Table. - */ - public var number:int; // 16bits - /** - * reverved value, must be '1' - */ - public var const1_value:int; //3bits - /** - * program_map_PID/network_PID 13bits - * network_PID - The network_PID is a 13-bit field, which is used only in conjunction with the value of the - * program_number set to 0x0000, specifies the PID of the Transport Stream packets which shall contain the Network - * Information Table. The value of the network_PID field is defined by the user, but shall only take values as specified in - * Table 2-3. The presence of the network_PID is optional. - */ - public var pid:int; //13bits - - public function SrsTsPayloadPATProgram(n:int, p:int) - { - number = n; - pid = p; - const1_value = 0x07; - } - - public function decode(stream:ByteArray):void - { - // atleast 4B for PAT program specified - if (stream.bytesAvailable < 4) { - throw new Error("ts: demux PAT failed."); - } - - var tmpv:int = stream.readUnsignedInt(); - number = ((tmpv >> 16) & 0xFFFF); - const1_value = ((tmpv >> 13) & 0x07); - pid = (tmpv & 0x1FFF); - - return; - } -}; - -/** - * the PAT payload of PSI ts packet. - * 2.4.4.3 Program association Table, hls-mpeg-ts-iso13818-1.pdf, page 61 - * The Program Association Table provides the correspondence between a program_number and the PID value of the - * Transport Stream packets which carry the program definition. The program_number is the numeric label associated with - * a program. - */ -class SrsTsPayloadPAT extends SrsTsPayloadPSI -{ - // 2B - /** - * This is a 16-bit field which serves as a label to identify this Transport Stream from any other - * multiplex within a network. Its value is defined by the user. - */ - public var transport_stream_id:int; //16bits - - // 1B - /** - * reverved value, must be '1' - */ - public var const3_value:int; //2bits - /** - * This 5-bit field is the version number of the whole Program Association Table. The version number - * shall be incremented by 1 modulo 32 whenever the definition of the Program Association Table changes. When the - * current_next_indicator is set to '1', then the version_number shall be that of the currently applicable Program Association - * Table. When the current_next_indicator is set to '0', then the version_number shall be that of the next applicable Program - * Association Table. - */ - public var version_number:int; //5bits - /** - * A 1-bit indicator, which when set to '1' indicates that the Program Association Table sent is - * currently applicable. When the bit is set to '0', it indicates that the table sent is not yet applicable and shall be the next - * table to become valid. - */ - public var current_next_indicator:int; //1bit - - // 1B - /** - * This 8-bit field gives the number of this section. The section_number of the first section in the - * Program Association Table shall be 0x00. It shall be incremented by 1 with each additional section in the Program - * Association Table. - */ - public var section_number:int; //8bits - - // 1B - /** - * This 8-bit field specifies the number of the last section (that is, the section with the highest - * section_number) of the complete Program Association Table. - */ - public var last_section_number:int; //8bits - - // multiple 4B program data, elem is SrsTsPayloadPATProgram - public var programs:Array; - - public function SrsTsPayloadPAT(p:SrsTsPacket) - { - super(p); - - const3_value = 3; - programs = new Array(); - } - - override protected function psi_decode(stream:ByteArray):void - { - // atleast 5B for PAT specified - if (stream.bytesAvailable < 5) { - throw new Error("ts: demux PAT failed."); - } - - var pos:uint = stream.position; - - // 2B - transport_stream_id = stream.readUnsignedShort(); - - // 1B - var cniv:int = stream.readUnsignedByte(); - - const3_value = (cniv >> 6) & 0x03; - version_number = (cniv >> 1) & 0x1F; - current_next_indicator = cniv & 0x01; - - // TODO: FIXME: check the indicator. - - // 1B - section_number = stream.readUnsignedByte(); - // 1B - last_section_number = stream.readUnsignedByte(); - - // multiple 4B program data. - var program_bytes:int = section_length - 4 - (stream.position - pos); - for (var i:int = 0; i < program_bytes; i += 4) { - var program:SrsTsPayloadPATProgram = new SrsTsPayloadPATProgram(0, 0); - program.decode(stream); - - // update the apply pid table. - packet.context.setChannel(program.pid, SrsTsPidApply.PMT, SrsTsStream.Reserved); - - programs.push(program); - } - - // update the apply pid table. - packet.context.setChannel(packet.pid, SrsTsPidApply.PAT, SrsTsStream.Reserved); - - return; - } -}; - -/** - * the esinfo for PMT program. - */ -class SrsTsPayloadPMTESInfo -{ - // 1B - /** - * This is an 8-bit field specifying the type of program element carried within the packets with the PID - * whose value is specified by the elementary_PID. The values of stream_type are specified in Table 2-29. - */ - public var stream_type:SrsTsStream; //8bits - - // 2B - /** - * reverved value, must be '1' - */ - public var const1_value0:int; //3bits - /** - * This is a 13-bit field specifying the PID of the Transport Stream packets which carry the associated - * program element. - */ - public var elementary_PID:int; //13bits - - // (2+x)B - /** - * reverved value, must be '1' - */ - public var const1_value1:int; //4bits - /** - * This is a 12-bit field, the first two bits of which shall be '00'. The remaining 10 bits specify the number - * of bytes of the descriptors of the associated program element immediately following the ES_info_length field. - */ - public var ES_info_length:int; //12bits - public var ES_info:ByteArray; //[ES_info_length] bytes. - - public function SrsTsPayloadPMTESInfo(st:SrsTsStream, epid:int) - { - stream_type = st; - elementary_PID = epid; - - const1_value0 = 7; - const1_value1 = 0x0f; - ES_info_length = 0; - ES_info = null; - } - - public function decode(stream:ByteArray):void - { - // 5B - if (stream.bytesAvailable < 5) { - throw new Error("ts: demux PMT es info failed."); - } - - stream_type = SrsTsStream.parse(stream.readUnsignedByte()); - - var epv:int = stream.readUnsignedShort(); - const1_value0 = (epv >> 13) & 0x07; - elementary_PID = epv & 0x1FFF; - - var eilv:int = stream.readUnsignedShort(); - const1_value1 = (epv >> 12) & 0x0f; - ES_info_length = eilv & 0x0FFF; - - if (ES_info_length > 0) { - if (stream.bytesAvailable < ES_info_length) { - throw new Error("ts: demux PMT es info data failed."); - } - ES_info = new ByteArray(); - stream.readBytes(ES_info, 0, ES_info_length); - } - } -}; - -/** - * the PMT payload of PSI ts packet. - * 2.4.4.8 Program Map Table, hls-mpeg-ts-iso13818-1.pdf, page 64 - * The Program Map Table provides the mappings between program numbers and the program elements that comprise - * them. A single instance of such a mapping is referred to as a "program definition". The program map table is the - * complete collection of all program definitions for a Transport Stream. This table shall be transmitted in packets, the PID - * values of which are selected by the encoder. More than one PID value may be used, if desired. The table is contained in - * one or more sections with the following syntax. It may be segmented to occupy multiple sections. In each section, the - * section number field shall be set to zero. Sections are identified by the program_number field. - */ -class SrsTsPayloadPMT extends SrsTsPayloadPSI -{ - // 2B - /** - * program_number is a 16-bit field. It specifies the program to which the program_map_PID is - * applicable. One program definition shall be carried within only one TS_program_map_section. This implies that a - * program definition is never longer than 1016 (0x3F8). See Informative Annex C for ways to deal with the cases when - * that length is not sufficient. The program_number may be used as a designation for a broadcast channel, for example. By - * describing the different program elements belonging to a program, data from different sources (e.g. sequential events) - * can be concatenated together to form a continuous set of streams using a program_number. For examples of applications - * refer to Annex C. - */ - public var program_number:int; //16bits - - // 1B - /** - * reverved value, must be '1' - */ - public var const1_value0:int; //2bits - /** - * This 5-bit field is the version number of the TS_program_map_section. The version number shall be - * incremented by 1 modulo 32 when a change in the information carried within the section occurs. Version number refers - * to the definition of a single program, and therefore to a single section. When the current_next_indicator is set to '1', then - * the version_number shall be that of the currently applicable TS_program_map_section. When the current_next_indicator - * is set to '0', then the version_number shall be that of the next applicable TS_program_map_section. - */ - public var version_number:int; //5bits - /** - * A 1-bit field, which when set to '1' indicates that the TS_program_map_section sent is - * currently applicable. When the bit is set to '0', it indicates that the TS_program_map_section sent is not yet applicable - * and shall be the next TS_program_map_section to become valid. - */ - public var current_next_indicator:int; //1bit - - // 1B - /** - * The value of this 8-bit field shall be 0x00. - */ - public var section_number:int; //8bits - - // 1B - /** - * The value of this 8-bit field shall be 0x00. - */ - public var last_section_number:int; //8bits - - // 2B - /** - * reverved value, must be '1' - */ - public var const1_value1:int; //3bits - /** - * This is a 13-bit field indicating the PID of the Transport Stream packets which shall contain the PCR fields - * valid for the program specified by program_number. If no PCR is associated with a program definition for private - * streams, then this field shall take the value of 0x1FFF. Refer to the semantic definition of PCR in 2.4.3.5 and Table 2-3 - * for restrictions on the choice of PCR_PID value. - */ - public var PCR_PID:int; //13bits - - // 2B - public var const1_value2:int; //4bits - /** - * This is a 12-bit field, the first two bits of which shall be '00'. The remaining 10 bits specify the - * number of bytes of the descriptors immediately following the program_info_length field. - */ - public var program_info_length:int; //12bits - public var program_info_desc:ByteArray; //[program_info_length]bytes - - // array of TSPMTESInfo, elem is SrsTsPayloadPMTESInfo object. - public var infos:Array; - - public function SrsTsPayloadPMT(p:SrsTsPacket) - { - super(p); - - const1_value0 = 3; - const1_value1 = 7; - const1_value2 = 0x0f; - program_info_length = 0; - program_info_desc = null; - infos = new Array(); - } - - override protected function psi_decode(stream:ByteArray):void - { - // atleast 9B for PMT specified - if (stream.bytesAvailable < 9) { - throw new Error("ts: demux PMT failed."); - } - - // 2B - program_number = stream.readUnsignedShort(); - - // 1B - var cniv:int = stream.readUnsignedByte(); - - const1_value0 = (cniv >> 6) & 0x03; - version_number = (cniv >> 1) & 0x1F; - current_next_indicator = cniv & 0x01; - - // 1B - section_number = stream.readUnsignedByte(); - - // 1B - last_section_number = stream.readUnsignedByte(); - - // 2B - var ppv:int = stream.readUnsignedShort(); - const1_value1 = (ppv >> 13) & 0x07; - PCR_PID = ppv & 0x1FFF; - - // 2B - var pilv:int = stream.readUnsignedShort(); - const1_value2 = (pilv >> 12) & 0x0F; - program_info_length = pilv & 0xFFF; - - if (program_info_length > 0) { - if (stream.bytesAvailable < program_info_length) { - throw new Error("ts: demux PMT program info failed."); - } - - program_info_desc = new ByteArray(); - stream.readBytes(program_info_desc, 0, program_info_length); - } - - // [section_length] - 4(CRC) - 9B - [program_info_length] - var ES_EOF_pos:uint = stream.position + section_length - 4 - 9 - program_info_length; - while (stream.position < ES_EOF_pos) { - var info:SrsTsPayloadPMTESInfo = new SrsTsPayloadPMTESInfo(SrsTsStream.Reserved, 0); - infos.push(info); - - info.decode(stream); - - // update the apply pid table - switch (info.stream_type) { - case SrsTsStream.VideoH264: - case SrsTsStream.VideoMpeg4: - packet.context.setChannel(info.elementary_PID, SrsTsPidApply.Video, info.stream_type); - break; - case SrsTsStream.AudioAAC: - case SrsTsStream.AudioAC3: - case SrsTsStream.AudioDTS: - case SrsTsStream.AudioMp3: - packet.context.setChannel(info.elementary_PID, SrsTsPidApply.Audio, info.stream_type); - break; - default: - // warn("ts: drop pid=%#x, stream=%#x", info->elementary_PID, info->stream_type); - break; - } - } - - // update the apply pid table. - packet.context.setChannel(packet.pid, SrsTsPidApply.PMT, SrsTsStream.Reserved); - packet.context.on_pmt_parsed(); - } -}; - -/** - * the m3u8 parser. - */ -class M3u8 -{ - private var _hls:HlsCodec; - private var _log:ILogger = new TraceLogger("HLS"); - - private var _tses:Array; - private var _duration:Number; - private var _seq_no:Number; - private var _url:String; - // when variant, all ts url is sub m3u8 url. - private var _variant:Boolean; - - public function M3u8(hls:HlsCodec) - { - _hls = hls; - _tses = new Array(); - _duration = 0; - _seq_no = 0; - _variant = false; - } - - public function getTsUrl(index:Number):String - { - if (index >= _tses.length) { - throw new Error("ts index overflow, index=" + index + ", max=" + _tses.length); - } - - var url:String = _tses[index].url; - - // if absolute url, return. - if (Utility.stringStartswith(url, "http://")) { - return url; - } - - // add prefix of m3u8. - if (_url.lastIndexOf("/") >= 0) { - url = _url.substr(0, _url.lastIndexOf("/") + 1) + url; - } - return url; - } - - public function parse(url:String, v:String):void - { - // TODO: FIXME: reset the m3u8 when parse. - _tses = new Array(); - _duration = 0; - _url = url; - _variant = false; - - var ptl:String = null; - var td:Number = 0; - var seq_no:Number = 0; - - var lines:Array = v.split("\n"); - for (var i:int = 0; i < lines.length; i++) { - var line:String = String(lines[i]).replace("\r", "").replace(" ", ""); - - // #EXT-X-VERSION:3 - // the version must be 3.0 - if (Utility.stringStartswith(line, "#EXT-X-VERSION:")) { - if (!Utility.stringEndswith(line, ":3")) { - _log.warn("m3u8 3.0 required, actual is {0}", line); - } - continue; - } - - // #EXT-X-STREAM-INF:BANDWIDTH=3000000 - if (Utility.stringStartswith(line, "#EXT-X-STREAM-INF:")) { - _variant = true; - var sub_m3u8_url:String = lines[++i]; - _tses.push({ - duration: 0, - url: sub_m3u8_url - }); - continue; - } - - // #EXT-X-PLAYLIST-TYPE:VOD - // the playlist type, vod or nothing. - if (Utility.stringStartswith(line, "#EXT-X-PLAYLIST-TYPE:")) { - ptl = line; - continue; - } - - // #EXT-X-MEDIA-SEQUENCE:55 - // the seq no of first ts. - if (Utility.stringStartswith(line, "#EXT-X-MEDIA-SEQUENCE:")) { - seq_no = Number(line.substr("#EXT-X-MEDIA-SEQUENCE:".length)); - continue; - } - - // #EXT-X-TARGETDURATION:12 - // the target duration is required. - if (Utility.stringStartswith(line, "#EXT-X-TARGETDURATION:")) { - td = Number(line.substr("#EXT-X-TARGETDURATION:".length)); - continue; - } - - // #EXT-X-ENDLIST - // parse completed. - if (line == "#EXT-X-ENDLIST") { - break; - } - - // #EXTINF:11.401, - // livestream-5.ts - // parse each ts entry, expect current line is inf. - if (!Utility.stringStartswith(line, "#EXTINF:")) { - continue; - } - // expect next line is url. - if (i >= lines.length - 1) { - _log.warn("ts entry unexpected eof, inf={0}", line); - break; - } - if (line.indexOf(",") >= 0) { - line = line.split(",")[0]; - } - var ts_duration:Number = Number(line.substr("#EXTINF:".length).replace(",", "")); - var ts_url:String = lines[++i]; - - _duration += ts_duration; - _tses.push({ - duration: ts_duration, - url: ts_url - }); - } - _seq_no = seq_no; - - // for vod, must specifies the playlist type to vod. - if (false) { - _log.warn("vod required the playlist type, actual is {0}", ptl); - } - - if (false) { - _log.info("hls got {0}B {1}L m3u8, td={2}, ts={3}", v.length, lines.length, td, _tses.length); - } else { - _log.debug("hls got {0}B {1}L m3u8, td={2}, ts={3}", v.length, lines.length, td, _tses.length); - } - } - - public function get tsCount():Number - { - return _tses.length; - } - - public function get duration():Number - { - return _duration; - } - - public function get seq_no():Number - { - return _seq_no; - } - - public function get variant():Boolean - { - return _variant; - } -} diff --git a/trunk/research/players/srs_player/src/Player.as b/trunk/research/players/srs_player/src/Player.as deleted file mode 100644 index e085b683e..000000000 --- a/trunk/research/players/srs_player/src/Player.as +++ /dev/null @@ -1,210 +0,0 @@ -package -{ - import flash.display.Sprite; - import flash.display.StageAlign; - import flash.display.StageDisplayState; - import flash.display.StageScaleMode; - import flash.events.Event; - import flash.events.FullScreenEvent; - import flash.events.MouseEvent; - import flash.events.NetStatusEvent; - import flash.events.TimerEvent; - import flash.external.ExternalInterface; - import flash.media.SoundTransform; - import flash.media.Video; - import flash.net.NetConnection; - import flash.net.NetStream; - import flash.net.URLVariables; - import flash.system.Security; - import flash.ui.ContextMenu; - import flash.ui.ContextMenuItem; - import flash.utils.Timer; - import flash.utils.getTimer; - import flash.utils.setTimeout; - - import flashx.textLayout.formats.Float; - - /** - * common player to play rtmp/flv stream, - * use system NetStream. - */ - public class Player - { - // refresh every ts_fragment_seconds*M3u8RefreshRatio - public static var M3u8RefreshRatio:Number = 0.3; - - // parse ts every this ms. - public static var TsParseAsyncInterval:Number = 80; - - private var js_id:String = null; - - // play param url. - private var user_url:String = null; - - private var media_stream:NetStream = null; - private var media_conn:NetConnection = null; - - private var owner:srs_player = null; - - public function Player(o:srs_player) { - owner = o; - } - - public function init(flashvars:Object):void { - this.js_id = flashvars.id; - } - - public function stream():NetStream { - return this.media_stream; - } - - private function dumps_object(obj:Object):String { - var smr:String = ""; - for (var k:String in obj) { - smr += k + "=" + obj[k] + ", "; - } - - return smr; - } - - public function play(url:String):void { - owner.on_player_status("init", "Ready to play"); - - var streamName:String; - this.user_url = url; - - this.media_conn = new NetConnection(); - this.media_conn.client = {}; - this.media_conn.client.onBWDone = function():void {}; - this.media_conn.addEventListener(NetStatusEvent.NET_STATUS, function(evt:NetStatusEvent):void { - log("NetConnection: type=" + evt.type + ", bub=" + evt.bubbles + ", can=" + evt.cancelable - + ", info is " + dumps_object(evt.info)); - - if (evt.info.hasOwnProperty("data") && evt.info.data) { - owner.on_player_metadata(evt.info.data); - } - - // reject by server, maybe redirect. - if (evt.info.code == "NetConnection.Connect.Rejected") { - // RTMP 302 redirect. - if (evt.info.hasOwnProperty("ex") && evt.info.ex.code == 302) { - streamName = url.substr(url.lastIndexOf("/") + 1); - url = evt.info.ex.redirect + "/" + streamName; - log("Async RTMP 302 Redirect to: " + url); - - // notify server. - media_conn.call("Redirected", null, evt.info.ex.redirect); - - // do 302. - owner.on_player_302(url); - return; - } - - owner.on_player_status("rejected", "Server reject play"); - close(); - } - - if (evt.info.code == "NetConnection.Connect.Success") { - owner.on_player_status("connected", "Connected at server"); - } - if (evt.info.code == "NetConnection.Connect.Closed") { - close(); - } - if (evt.info.code == "NetConnection.Connect.Failed") { - owner.on_player_status("failed", "Connect to server failed."); - close(); - } - - // TODO: FIXME: failed event. - if (evt.info.code != "NetConnection.Connect.Success") { - return; - } - - if (url.indexOf(".m3u8") > 0) { - media_stream = new HlsNetStream(M3u8RefreshRatio, TsParseAsyncInterval, media_conn); - } else { - media_stream = new NetStream(media_conn); - } - media_stream.addEventListener(NetStatusEvent.NET_STATUS, function(evt:NetStatusEvent):void { - log("NetStream: type=" + evt.type + ", bub=" + evt.bubbles + ", can=" + evt.cancelable - + ", info is " + dumps_object(evt.info)); - - if (evt.info.code == "NetStream.Play.Start") { - owner.on_player_status("play", "Start to play stream"); - } - if (evt.info.code == "NetStream.Play.StreamNotFound") { - owner.on_player_status("rejected", "Stream not found"); - close(); - } - - if (evt.info.code == "NetStream.Video.DimensionChange") { - owner.on_player_dimension_change(); - } else if (evt.info.code == "NetStream.Buffer.Empty") { - owner.on_player_buffer_empty(); - } else if (evt.info.code == "NetStream.Buffer.Full") { - owner.on_player_buffer_full(); - } - - // TODO: FIXME: failed event. - }); - - // setup stream before play. - owner.on_player_before_play(); - - if (url.indexOf("http") == 0) { - media_stream.play(url); - } else { - streamName = url.substr(url.lastIndexOf("/") + 1); - media_stream.play(streamName); - } - - owner.on_player_play(); - }); - - if (url.indexOf("http") == 0) { - this.media_conn.connect(null); - } else { - var tcUrl:String = this.user_url.substr(0, this.user_url.lastIndexOf("/")); - streamName = url.substr(url.lastIndexOf("/") + 1); - - // parse vhost from stream query. - if (streamName.indexOf("?") >= 0) { - var uv:URLVariables = new URLVariables(user_url.substr(user_url.indexOf("?") + 1)); - var domain:String = uv["domain"]; - if (!domain) { - domain = uv["vhost"]; - } - if (domain) { - tcUrl += "?vhost=" + domain; - } - } - - this.media_conn.connect(tcUrl); - } - } - - public function close():void { - var notify:Boolean = false; - - if (this.media_stream) { - this.media_stream.close(); - this.media_stream = null; - notify = true; - } - - if (this.media_conn) { - this.media_conn.close(); - this.media_conn = null; - notify = true; - } - - if (notify) { - owner.on_player_status("closed", "Server closed."); - } - } - - private function log(msg:String):void { - Utility.log(js_id, msg); - } - } -} \ No newline at end of file diff --git a/trunk/research/players/srs_player/src/Utility.as b/trunk/research/players/srs_player/src/Utility.as deleted file mode 100644 index 7707954c2..000000000 --- a/trunk/research/players/srs_player/src/Utility.as +++ /dev/null @@ -1,51 +0,0 @@ -package -{ - import flash.external.ExternalInterface; - import flash.utils.setTimeout; - - /** - * the utility functions. - */ - public class Utility - { - /** - * total log. - */ - public static var logData:String = ""; - - /** - * whether string s endswith f. - */ - public static function stringEndswith(s:String, f:String):Boolean { - return s && f && s.indexOf(f) == s.length - f.length; - } - - /** - * whether string s startswith f. - */ - public static function stringStartswith(s:String, f:String):Boolean { - return s && f && s.indexOf(f) == 0; - } - - /** - * write log to trace and console.log. - * @param msg the log message. - */ - public static function log(js_id:String, msg:String):void { - if (js_id) { - msg = "[" + new Date() +"][srs-player][" + js_id + "] " + msg; - } - - logData += msg + "\n"; - - trace(msg); - - if (!flash.external.ExternalInterface.available) { - flash.utils.setTimeout(log, 300, null, msg); - return; - } - - ExternalInterface.call("console.log", msg); - } - } -} \ No newline at end of file diff --git a/trunk/research/players/srs_player/src/srs_player.as b/trunk/research/players/srs_player/src/srs_player.as deleted file mode 100755 index 9f0e424b8..000000000 --- a/trunk/research/players/srs_player/src/srs_player.as +++ /dev/null @@ -1,668 +0,0 @@ -package -{ - import flash.display.Sprite; - import flash.display.StageAlign; - import flash.display.StageDisplayState; - import flash.display.StageScaleMode; - import flash.events.Event; - import flash.events.FullScreenEvent; - import flash.events.MouseEvent; - import flash.events.NetStatusEvent; - import flash.events.TimerEvent; - import flash.external.ExternalInterface; - import flash.media.SoundTransform; - import flash.media.Video; - import flash.net.NetConnection; - import flash.net.NetStream; - import flash.system.Security; - import flash.ui.ContextMenu; - import flash.ui.ContextMenuItem; - import flash.utils.Timer; - import flash.utils.getTimer; - import flash.utils.setTimeout; - - import flashx.textLayout.formats.Float; - - public class srs_player extends Sprite - { - // user set id. - private var js_id:String = null; - // user set callback - private var js_on_player_ready:String = null; - private var js_on_player_metadata:String = null; - private var js_on_player_timer:String = null; - private var js_on_player_empty:String = null; - private var js_on_player_full:String = null; - private var js_on_player_status:String = null; - - // play param, user set width and height - private var user_w:int = 0; - private var user_h:int = 0; - private var user_buffer_time:Number = 0; - private var user_max_buffer_time:Number = 0; - private var user_volume:Number = 0; - // user set dar den:num - private var user_dar_den:int = 0; - private var user_dar_num:int = 0; - // user set fs(fullscreen) refer and percent. - private var user_fs_refer:String = null; - private var user_fs_percent:int = 0; - - // media specified. - private var media_video:Video = null; - private var media_metadata:Object = {}; - private var media_timer:Timer = new Timer(300); - - // controls. - // flash donot allow js to set to fullscreen, - // only allow user click to enter fullscreen. - private var control_fs_mask:Sprite = new Sprite(); - - // the common player to play stream. - private var player:Player = null; - // the flashvars config. - private var config:Object = null; - - public function srs_player() - { - if (!this.stage) { - this.addEventListener(Event.ADDED_TO_STAGE, this.system_on_add_to_stage); - } else { - this.system_on_add_to_stage(null); - } - } - - /** - * system event callback, when this control added to stage. - * the main function. - */ - private function system_on_add_to_stage(evt:Event):void { - this.removeEventListener(Event.ADDED_TO_STAGE, this.system_on_add_to_stage); - - this.stage.align = StageAlign.TOP_LEFT; - this.stage.scaleMode = StageScaleMode.NO_SCALE; - - this.stage.addEventListener(FullScreenEvent.FULL_SCREEN, this.user_on_stage_fullscreen); - - Security.allowDomain("*"); - - this.addChild(this.control_fs_mask); - this.control_fs_mask.buttonMode = true; - this.control_fs_mask.addEventListener(MouseEvent.CLICK, user_on_click_video); - - this.contextMenu = new ContextMenu(); - this.contextMenu.hideBuiltInItems(); - - var flashvars:Object = this.root.loaderInfo.parameters; - - if (!flashvars.hasOwnProperty("id")) { - throw new Error("must specifies the id"); - } - - this.config = flashvars; - this.js_id = flashvars.id; - this.js_on_player_ready = flashvars.on_player_ready; - this.js_on_player_metadata = flashvars.on_player_metadata; - this.js_on_player_timer = flashvars.on_player_timer; - this.js_on_player_empty = flashvars.on_player_empty; - this.js_on_player_full = flashvars.on_player_full; - this.js_on_player_status = flashvars.on_player_status; - - this.media_timer.addEventListener(TimerEvent.TIMER, this.system_on_timer); - this.media_timer.start(); - - flash.utils.setTimeout(this.system_on_js_ready, 0); - } - - /** - * system callack event, when js ready, register callback for js. - * the actual main function. - */ - private function system_on_js_ready():void { - if (!flash.external.ExternalInterface.available) { - log("js not ready, try later."); - flash.utils.setTimeout(this.system_on_js_ready, 100); - return; - } - - flash.external.ExternalInterface.addCallback("__play", this.js_call_play); - flash.external.ExternalInterface.addCallback("__stop", this.js_call_stop); - flash.external.ExternalInterface.addCallback("__pause", this.js_call_pause); - flash.external.ExternalInterface.addCallback("__resume", this.js_call_resume); - flash.external.ExternalInterface.addCallback("__set_dar", this.js_call_set_dar); - flash.external.ExternalInterface.addCallback("__set_fs", this.js_call_set_fs_size); - flash.external.ExternalInterface.addCallback("__set_bt", this.js_call_set_bt); - flash.external.ExternalInterface.addCallback("__set_mbt", this.js_call_set_mbt); - flash.external.ExternalInterface.addCallback("__dump_log", this.js_call_dump_log); - - flash.external.ExternalInterface.call(this.js_on_player_ready, this.js_id); - } - - /** - * system callack event, timer to do some regular tasks. - */ - private function system_on_timer(evt:TimerEvent):void { - if (!player) { - return; - } - - var ms:NetStream = player.stream(); - - if (!ms) { - //log("stream is null, ignore timer event."); - return; - } - - var rtime:Number = flash.utils.getTimer(); - var bitrate:Number = Number((ms.info.videoBytesPerSecond + ms.info.audioBytesPerSecond) * 8 / 1000); - log("on timer, time=" + ms.time.toFixed(2) + "s, buffer=" + ms.bufferLength.toFixed(2) + "s" - + ", bitrate=" + bitrate.toFixed(1) + "kbps" - + ", fps=" + ms.currentFPS.toFixed(1) - + ", rtime=" + rtime.toFixed(0) - ); - flash.external.ExternalInterface.call( - this.js_on_player_timer, this.js_id, ms.time, ms.bufferLength, - bitrate, ms.currentFPS, rtime - ); - } - - /** - * system callback event, when stream is empty. - */ - private function system_on_buffer_empty():void { - var time:Number = flash.utils.getTimer(); - log("stream is empty at " + time + "ms"); - flash.external.ExternalInterface.call(this.js_on_player_empty, this.js_id, time); - } - private function system_on_buffer_full():void { - var time:Number = flash.utils.getTimer(); - log("stream is full at " + time + "ms"); - flash.external.ExternalInterface.call(this.js_on_player_full, this.js_id, time); - } - - /** - * system callack event, when got metadata from stream. - * or got video dimension change event(the DAR notification), to update the metadata manually. - */ - private function system_on_metadata(metadata:Object):void { - if (!media_metadata) { - media_metadata = {}; - } - for (var k:String in metadata) { - media_metadata[k] = metadata[k]; - } - - // update the debug info. - on_debug_info(media_metadata); - update_context_items(); - - // for js. - var obj:Object = __get_video_size_object(); - - obj.server = 'srs'; - obj.contributor = 'winlin'; - - if (srs_server != null) { - obj.server = srs_server; - } - if (srs_primary != null) { - obj.contributor = srs_primary; - } - if (srs_authors != null) { - obj.contributor = srs_authors; - } - if (srs_id != null) { - obj.cid = srs_id; - } - if (srs_pid != null) { - obj.pid = srs_pid; - } - if (srs_server_ip != null) { - obj.ip = srs_server_ip; - } - - var s:String = ""; - for (var key:String in obj) { - s += key + "=" + obj[key] + " "; - } - log("metadata is " + s); - - var code:int = flash.external.ExternalInterface.call(js_on_player_metadata, js_id, obj); - if (code != 0) { - throw new Error("callback on_player_metadata failed. code=" + code); - } - } - - /** - * player callack event, when user click video to enter or leave fullscreen. - */ - private function user_on_stage_fullscreen(evt:FullScreenEvent):void { - if (!evt.fullScreen) { - __execute_user_set_dar(); - } else { - __execute_user_enter_fullscreen(); - } - } - - /** - * user event callback, js cannot enter the fullscreen mode, user must click to. - */ - private function user_on_click_video(evt:MouseEvent):void { - if (!this.stage.allowsFullScreen) { - log("donot allow fullscreen."); - return; - } - - // enter fullscreen to get the fullscreen size correctly. - if (this.stage.displayState == StageDisplayState.FULL_SCREEN) { - this.stage.displayState = StageDisplayState.NORMAL; - } else { - this.stage.displayState = StageDisplayState.FULL_SCREEN; - } - } - - /** - * function for js to call: to pause the stream. ignore if not play. - */ - private function js_call_pause():void { - if (player && player.stream()) { - player.stream().pause(); - log("user pause play"); - } - } - - /** - * function for js to call: to resume the stream. ignore if not play. - */ - private function js_call_resume():void { - if (player && player.stream()) { - player.stream().resume(); - log("user resume play"); - } - } - - /** - * dumps all log data. - */ - private function js_call_dump_log():String { - return Utility.logData; - } - - /** - * to set the DAR, for example, DAR=16:9 where num=16,den=9. - * @param num, for example, 16. - * use metadata width if 0. - * use user specified width if -1. - * @param den, for example, 9. - * use metadata height if 0. - * use user specified height if -1. - */ - private function js_call_set_dar(num:int, den:int):void { - user_dar_num = num; - user_dar_den = den; - - flash.utils.setTimeout(__execute_user_set_dar, 0); - log("user set dar to " + num + "/" + den); - } - - /** - * set the fullscreen size data. - * @refer the refer fullscreen mode. it can be: - * video: use video orignal size. - * screen: use screen size to rescale video. - * @param percent, the rescale percent, where - * 100 means 100%. - */ - private function js_call_set_fs_size(refer:String, percent:int):void { - user_fs_refer = refer; - user_fs_percent = percent; - log("user set refer to " + refer + ", percent to" + percent); - } - - /** - * set the stream buffer time in seconds. - * @buffer_time the buffer time in seconds. - */ - private function js_call_set_bt(buffer_time:Number):void { - if (player && player.stream()) { - player.stream().bufferTime = buffer_time; - log("user set bufferTime to " + buffer_time.toFixed(2) + "s"); - } - } - - /** - * set the max stream buffer time in seconds. - * @max_buffer_time the max buffer time in seconds. - * @remark this is the key feature for realtime communication by flash. - */ - private function js_call_set_mbt(max_buffer_time:Number):void { - if (player && player.stream()) { - player.stream().bufferTimeMax = max_buffer_time; - log("user set bufferTimeMax to " + max_buffer_time.toFixed(2) + "s"); - } - } - - /** - * function for js to call: to stop the stream. ignore if not play. - */ - private function js_call_stop():void { - if (this.media_video) { - this.removeChild(this.media_video); - this.media_video = null; - } - - if (player) { - player.close(); - player = null; - } - log("player stopped"); - } - - // srs infos - private var srs_server:String = null; - private var srs_primary:String = null; - private var srs_authors:String = null; - private var srs_id:String = null; - private var srs_pid:String = null; - private var srs_server_ip:String = null; - private function update_context_items():void { - // for context menu - var customItems:Array = [new ContextMenuItem("SrsPlayer")]; - if (srs_server != null) { - customItems.push(new ContextMenuItem("Server: " + srs_server)); - } - if (srs_primary != null) { - customItems.push(new ContextMenuItem("Primary: " + srs_primary)); - } - if (srs_authors != null) { - customItems.push(new ContextMenuItem("Authors: " + srs_authors)); - } - if (srs_server_ip != null) { - customItems.push(new ContextMenuItem("SrsIp: " + srs_server_ip)); - } - if (srs_pid != null) { - customItems.push(new ContextMenuItem("SrsPid: " + srs_pid)); - } - if (srs_id != null) { - customItems.push(new ContextMenuItem("SrsId: " + srs_id)); - } - contextMenu.customItems = customItems; - } - - /** - * server can set the debug info in _result of RTMP connect, or metadata. - */ - private function on_debug_info(data:*):void { - if (data.hasOwnProperty("srs_server")) { - srs_server = data.srs_server; - } - if (data.hasOwnProperty("srs_primary")) { - srs_primary = data.srs_primary; - } - if (data.hasOwnProperty("srs_authors")) { - srs_authors = data.srs_authors; - } - if (data.hasOwnProperty("srs_id")) { - srs_id = data.srs_id; - } - if (data.hasOwnProperty("srs_pid")) { - srs_pid = data.srs_pid; - } - if (data.hasOwnProperty("srs_server_ip")) { - srs_server_ip = data.srs_server_ip; - } - } - - /** - * function for js to call: to play the stream. stop then play. - * @param url, the rtmp/http url to play. - * @param _width, the player width. - * @param _height, the player height. - * @param buffer_time, the buffer time in seconds. recommend to >=0.5 - * @param max_buffer_time, the max buffer time in seconds. recommend to 3 x buffer_time. - * @param volume, the volume, 0 is mute, 1 is 100%, 2 is 200%. - */ - private function js_call_play(url:String, _width:int, _height:int, buffer_time:Number, max_buffer_time:Number, volume:Number):void { - this.user_w = _width; - this.user_h = _height; - this.user_buffer_time = buffer_time; - this.user_max_buffer_time = max_buffer_time; - this.user_volume = volume; - log("start to play url: " + url + ", w=" + this.user_w + ", h=" + this.user_h - + ", buffer=" + buffer_time.toFixed(2) + "s, max_buffer=" + max_buffer_time.toFixed(2) + "s, volume=" + volume.toFixed(2) - ); - - js_call_stop(); - - // trim last ? - while (Utility.stringEndswith(url, "?")) { - url = url.substr(0, url.length - 1); - } - - // create player. - player = new Player(this); - - // init player by config. - player.init(config); - - // play the url. - player.play(url); - } - public function on_player_before_play():void { - if (!player) { - return; - } - - var ms:NetStream = player.stream(); - if (!ms) { - return; - } - - ms.soundTransform = new SoundTransform(user_volume); - ms.bufferTime = user_buffer_time; - ms.bufferTimeMax = user_max_buffer_time; - ms.client = {}; - ms.client.onMetaData = system_on_metadata; - } - public function on_player_play():void { - if (!player) { - return; - } - - media_video = new Video(); - media_video.width = user_w; - media_video.height = user_h; - media_video.attachNetStream(player.stream()); - media_video.smoothing = true; - addChild(media_video); - - __draw_black_background(user_w, user_h); - - // lowest layer, for mask to cover it. - setChildIndex(media_video, 0); - } - public function on_player_metadata(data:Object):void { - system_on_metadata(data); - } - public function on_player_302(url:String):void { - setTimeout(function():void{ - log("Async RTMP 302 Redirected."); - js_call_play(url, user_w, user_h, user_buffer_time, user_max_buffer_time, user_volume); - }, 1000); - } - public function on_player_dimension_change():void { - system_on_metadata(media_metadata); - } - public function on_player_buffer_empty():void { - system_on_buffer_empty(); - } - public function on_player_buffer_full():void { - system_on_buffer_full(); - } - - public function on_player_status(code:String, desc:String):void { - log("[STATUS] code=" + code + ", desc=" + desc); - flash.external.ExternalInterface.call(this.js_on_player_status, this.js_id, code, desc); - } - - /** - * get the "right" size of video, - * 1. initialize with the original video object size. - * 2. override with metadata size if specified. - * 3. override with codec size if specified. - */ - private function __get_video_size_object():Object { - if (!media_video) { - return {}; - } - var obj:Object = { - width: media_video.width, - height: media_video.height - }; - - // override with metadata size. - if (this.media_metadata.hasOwnProperty("width")) { - obj.width = this.media_metadata.width; - } - if (this.media_metadata.hasOwnProperty("height")) { - obj.height = this.media_metadata.height; - } - - // override with codec size. - if (media_video.videoWidth > 0) { - obj.width = media_video.videoWidth; - } - if (media_video.videoHeight > 0) { - obj.height = media_video.videoHeight; - } - - return obj; - } - - /** - * execute the enter fullscreen action. - */ - private function __execute_user_enter_fullscreen():void { - if (!user_fs_refer || user_fs_percent <= 0) { - return; - } - - // change to video size if refer to video. - var obj:Object = __get_video_size_object(); - - // get the DAR - var den:int = user_dar_den; - var num:int = user_dar_num; - - if (den == 0) { - den = obj.height; - } - if (den == -1) { - den = this.stage.fullScreenHeight; - } - - if (num == 0) { - num = obj.width; - } - if (num == -1) { - num = this.stage.fullScreenWidth; - } - - // for refer is screen. - if (user_fs_refer == "screen") { - obj = { - width: this.stage.fullScreenWidth, - height: this.stage.fullScreenHeight - }; - } - - // rescale to fs - __update_video_size(num, den, - obj.width * user_fs_percent / 100, - obj.height * user_fs_percent / 100, - this.stage.fullScreenWidth, this.stage.fullScreenHeight - ); - } - - /** - * for user set dar, or leave fullscreen to recover the dar. - */ - private function __execute_user_set_dar():void { - // get the DAR - var den:int = user_dar_den; - var num:int = user_dar_num; - - var obj:Object = __get_video_size_object(); - - if (den == 0) { - den = obj.height; - } - if (den == -1) { - den = this.user_h; - } - - if (num == 0) { - num = obj.width; - } - if (num == -1) { - num = this.user_w; - } - - __update_video_size(num, den, this.user_w, this.user_h, this.user_w, this.user_h); - } - - /** - * update the video width and height, - * according to the specifies DAR(den:num) and max size(w:h). - * set the position of video(x,y) specifies by size(sw:sh), - * and update the bg to size(sw:sh). - * @param _num/_den the DAR. use to rescale the player together with paper size. - * @param _w/_h the video draw paper size. used to rescale the player together with DAR. - * @param _sw/_wh the stage size, >= paper size. used to center the player. - */ - private function __update_video_size(_num:int, _den:int, _w:int, _h:int, _sw:int, _sh:int):void { - if (!this.media_video || _den <= 0 || _num <= 0) { - return; - } - - // set DAR. - // calc the height by DAR - var _height:int = _w * _den / _num; - if (_height <= _h) { - this.media_video.width = _w; - this.media_video.height = _height; - } else { - // height overflow, calc the width by DAR - var _width:int = _h * _num / _den; - - this.media_video.width = _width; - this.media_video.height = _h; - } - - // align center. - this.media_video.x = (_sw - this.media_video.width) / 2; - this.media_video.y = (_sh - this.media_video.height) / 2; - - __draw_black_background(_sw, _sh); - } - - /** - * draw black background and draw the fullscreen mask. - */ - private function __draw_black_background(_width:int, _height:int):void { - // draw black bg. - this.graphics.beginFill(0x00, 1.0); - this.graphics.drawRect(0, 0, _width, _height); - this.graphics.endFill(); - - // draw the fs mask. - this.control_fs_mask.graphics.beginFill(0xff0000, 0); - this.control_fs_mask.graphics.drawRect(0, 0, _width, _height); - this.control_fs_mask.graphics.endFill(); - } - - private function log(msg:String):void { - Utility.log(js_id, msg); - } - } -} diff --git a/trunk/research/players/srs_publisher/.actionScriptProperties b/trunk/research/players/srs_publisher/.actionScriptProperties deleted file mode 100755 index 9a40b6549..000000000 --- a/trunk/research/players/srs_publisher/.actionScriptProperties +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/trunk/research/players/srs_publisher/.project b/trunk/research/players/srs_publisher/.project deleted file mode 100755 index f31443569..000000000 --- a/trunk/research/players/srs_publisher/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - srs_publisher - - - - - - com.adobe.flexbuilder.project.flexbuilder - - - - - - com.adobe.flexbuilder.project.actionscriptnature - - diff --git a/trunk/research/players/srs_publisher/.settings/org.eclipse.core.resources.prefs b/trunk/research/players/srs_publisher/.settings/org.eclipse.core.resources.prefs deleted file mode 100755 index 6a306be03..000000000 --- a/trunk/research/players/srs_publisher/.settings/org.eclipse.core.resources.prefs +++ /dev/null @@ -1,3 +0,0 @@ -#Wed Dec 18 10:10:45 CST 2013 -eclipse.preferences.version=1 -encoding/=utf-8 diff --git a/trunk/research/players/srs_publisher/release/srs_publisher.swf b/trunk/research/players/srs_publisher/release/srs_publisher.swf deleted file mode 100755 index 24644897f4110afc074715285fe56576a2e88a86..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5641 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 diff --git a/trunk/research/players/srs_publisher/src/srs_publisher.as b/trunk/research/players/srs_publisher/src/srs_publisher.as deleted file mode 100755 index c7a47c5e7..000000000 --- a/trunk/research/players/srs_publisher/src/srs_publisher.as +++ /dev/null @@ -1,462 +0,0 @@ -package -{ - import flash.display.Sprite; - import flash.display.StageAlign; - import flash.display.StageScaleMode; - import flash.events.Event; - import flash.events.NetStatusEvent; - import flash.external.ExternalInterface; - import flash.media.Camera; - import flash.media.H264Profile; - import flash.media.H264VideoStreamSettings; - import flash.media.Microphone; - import flash.media.MicrophoneEnhancedMode; - import flash.media.MicrophoneEnhancedOptions; - import flash.media.SoundCodec; - import flash.media.Video; - import flash.net.NetConnection; - import flash.net.NetStream; - import flash.system.Security; - import flash.system.SecurityPanel; - import flash.ui.ContextMenu; - import flash.ui.ContextMenuItem; - import flash.utils.setTimeout; - - public class srs_publisher extends Sprite - { - // user set id. - private var js_id:String = null; - // user set callback - private var js_on_publisher_ready:String = null; - private var js_on_publisher_error:String = null; - private var js_on_publisher_warn:String = null; - - // publish param url. - private var user_url:String = null; - // play param, user set width and height - private var user_w:int = 0; - private var user_h:int = 0; - private var user_vcodec:Object = {}; - private var user_acodec:Object = {}; - - // media specified. - private var media_conn:NetConnection = null; - private var media_stream:NetStream = null; - private var media_video:Video = null; - private var media_camera:Camera = null; - private var media_microphone:Microphone = null; - - // error code. - private const error_camera_get:int = 100; - private const error_microphone_get:int = 101; - private const error_camera_muted:int = 102; - private const error_connection_closed:int = 103; - private const error_connection_failed:int = 104; - - public function srs_publisher() - { - if (!this.stage) { - this.addEventListener(Event.ADDED_TO_STAGE, this.system_on_add_to_stage); - } else { - this.system_on_add_to_stage(null); - } - } - - /** - * system event callback, when this control added to stage. - * the main function. - */ - private function system_on_add_to_stage(evt:Event):void { - this.removeEventListener(Event.ADDED_TO_STAGE, this.system_on_add_to_stage); - - this.stage.align = StageAlign.TOP_LEFT; - this.stage.scaleMode = StageScaleMode.NO_SCALE; - - this.contextMenu = new ContextMenu(); - this.contextMenu.hideBuiltInItems(); - - var flashvars:Object = this.root.loaderInfo.parameters; - - if (!flashvars.hasOwnProperty("id")) { - throw new Error("must specifies the id"); - } - - this.js_id = flashvars.id; - this.js_on_publisher_ready = flashvars.on_publisher_ready; - this.js_on_publisher_error = flashvars.on_publisher_error; - this.js_on_publisher_warn = flashvars.on_publisher_warn; - - // initialized size. - this.user_w = flashvars.width; - this.user_h = flashvars.height; - - // try to get the camera, if muted, alert the security and requires user to open it. - var c:Camera = Camera.getCamera(); - if (c.muted) { - Security.showSettings(SecurityPanel.PRIVACY); - } - - __show_local_camera(c); - - flash.utils.setTimeout(this.system_on_js_ready, 0); - } - - /** - * system callack event, when js ready, register callback for js. - * the actual main function. - */ - private function system_on_js_ready():void { - if (!flash.external.ExternalInterface.available) { - trace("js not ready, try later."); - flash.utils.setTimeout(this.system_on_js_ready, 100); - return; - } - - flash.external.ExternalInterface.addCallback("__publish", this.js_call_publish); - flash.external.ExternalInterface.addCallback("__stop", this.js_call_stop); - - var cameras:Array = Camera.names; - var microphones:Array = Microphone.names; - trace("retrieve system cameras(" + cameras + ") and microphones(" + microphones + ")"); - - flash.external.ExternalInterface.call(this.js_on_publisher_ready, this.js_id, cameras, microphones); - } - - /** - * notify the js an error occur. - */ - private function system_error(code:int, desc:String):void { - trace("system error, code=" + code + ", error=" + desc); - flash.external.ExternalInterface.call(this.js_on_publisher_error, this.js_id, code); - } - private function system_warn(code:int, desc:String):void { - trace("system warn, code=" + code + ", error=" + desc); - flash.external.ExternalInterface.call(this.js_on_publisher_warn, this.js_id, code); - } - - // srs infos - private var srs_server:String = null; - private var srs_primary:String = null; - private var srs_authors:String = null; - private var srs_id:String = null; - private var srs_pid:String = null; - private var srs_server_ip:String = null; - private function update_context_items():void { - // for context menu - var customItems:Array = [new ContextMenuItem("SrsPlayer")]; - if (srs_server != null) { - customItems.push(new ContextMenuItem("Server: " + srs_server)); - } - if (srs_primary != null) { - customItems.push(new ContextMenuItem("Primary: " + srs_primary)); - } - if (srs_authors != null) { - customItems.push(new ContextMenuItem("Authors: " + srs_authors)); - } - if (srs_server_ip != null) { - customItems.push(new ContextMenuItem("SrsIp: " + srs_server_ip)); - } - if (srs_pid != null) { - customItems.push(new ContextMenuItem("SrsPid: " + srs_pid)); - } - if (srs_id != null) { - customItems.push(new ContextMenuItem("SrsId: " + srs_id)); - } - contextMenu.customItems = customItems; - } - - /** - * publish stream to server. - * @param url a string indicates the rtmp url to publish. - * @param _width, the player width. - * @param _height, the player height. - * @param vcodec an object contains the video codec info. - * @param acodec an object contains the audio codec info. - */ - private function js_call_publish(url:String, _width:int, _height:int, vcodec:Object, acodec:Object):void { - trace("start to publish to " + url + ", vcodec " + JSON.stringify(vcodec) + ", acodec " + JSON.stringify(acodec)); - - this.user_url = url; - this.user_w = _width; - this.user_h = _height; - this.user_vcodec = vcodec; - this.user_acodec = acodec; - - this.js_call_stop(); - - // microphone and camera - var microphone:Microphone = null; - //microphone = Microphone.getEnhancedMicrophone(acodec.device_code); - if (!microphone) { - microphone = Microphone.getMicrophone(acodec.device_code); - } - if(microphone == null){ - this.system_error(this.error_microphone_get, "failed to open microphone " + acodec.device_code + "(" + acodec.device_name + ")"); - return; - } - // ignore muted, for flash will require user to access it. - - // Remark: the name is the index! - var camera:Camera = Camera.getCamera(vcodec.device_code); - if(camera == null){ - this.system_error(this.error_camera_get, "failed to open camera " + vcodec.device_code + "(" + vcodec.device_name + ")"); - return; - } - // ignore muted, for flash will require user to access it. - // but we still warn user. - if(camera && camera.muted){ - this.system_warn(this.error_camera_muted, "Access Denied, camera " + vcodec.device_code + "(" + vcodec.device_name + ") is muted"); - } - - this.media_camera = camera; - this.media_microphone = microphone; - - this.media_conn = new NetConnection(); - this.media_conn.client = {}; - this.media_conn.client.onBWDone = function():void {}; - this.media_conn.addEventListener(NetStatusEvent.NET_STATUS, function(evt:NetStatusEvent):void { - trace ("NetConnection: code=" + evt.info.code); - - if (evt.info.hasOwnProperty("data") && evt.info.data) { - if (evt.info.data.hasOwnProperty("srs_server")) { - srs_server = evt.info.data.srs_server; - } - if (evt.info.data.hasOwnProperty("srs_primary")) { - srs_primary = evt.info.data.srs_primary; - } - if (evt.info.data.hasOwnProperty("srs_authors")) { - srs_authors = evt.info.data.srs_authors; - } - if (evt.info.data.hasOwnProperty("srs_id")) { - srs_id = evt.info.data.srs_id; - } - if (evt.info.data.hasOwnProperty("srs_pid")) { - srs_pid = evt.info.data.srs_pid; - } - if (evt.info.data.hasOwnProperty("srs_server_ip")) { - srs_server_ip = evt.info.data.srs_server_ip; - } - update_context_items(); - } - - if (evt.info.code == "NetConnection.Connect.Closed") { - js_call_stop(); - system_error(error_connection_closed, "server closed the connection"); - return; - } - if (evt.info.code == "NetConnection.Connect.Failed") { - js_call_stop(); - system_error(error_connection_failed, "connect to server failed"); - return; - } - - // TODO: FIXME: failed event. - if (evt.info.code != "NetConnection.Connect.Success") { - return; - } - - media_stream = new NetStream(media_conn); - media_stream.client = {}; - media_stream.addEventListener(NetStatusEvent.NET_STATUS, function(evt:NetStatusEvent):void { - trace ("NetStream: code=" + evt.info.code); - - // TODO: FIXME: failed event. - }); - - __build_video_codec(media_stream, camera, vcodec); - __build_audio_codec(media_stream, microphone, acodec); - - if (media_microphone) { - media_stream.attachAudio(microphone); - } - if (media_camera) { - media_stream.attachCamera(camera); - } - - var streamName:String = url.substr(url.lastIndexOf("/")); - media_stream.publish(streamName); - - __show_local_camera(media_camera); - }); - - var tcUrl:String = this.user_url.substr(0, this.user_url.lastIndexOf("/")); - this.media_conn.connect(tcUrl); - } - - /** - * function for js to call: to stop the stream. ignore if not publish. - */ - private function js_call_stop():void { - if (this.media_stream) { - this.media_stream.attachAudio(null); - this.media_stream.attachCamera(null); - this.media_stream.close(); - this.media_stream = null; - } - if (this.media_conn) { - this.media_conn.close(); - this.media_conn = null; - } - } - - private function __build_audio_codec(stream:NetStream, m:Microphone, acodec:Object):void { - if (!m) { - return; - } - - // if no microphone, donot set the params. - if(m == null){ - return; - } - - // use default values. - var microEncodeQuality:int = 8; - var microRate:int = 22; // 22 === 22050 Hz - - trace("[Publish] audio encoding parameters: " - + "audio(microphone) codec=" + acodec.codec + "encodeQuality=" + microEncodeQuality - + ", rate=" + microRate + "(22050Hz)" - ); - - // The encoded speech quality when using the Speex codec. Possible values are from 0 to 10. The default value is 6. Higher numbers - // represent higher quality but require more bandwidth, as shown in the following table. The bit rate values that are listed represent - // net bit rates and do not include packetization overhead. - m.encodeQuality = microEncodeQuality; - - // The rate at which the microphone is capturing sound, in kHz. Acceptable values are 5, 8, 11, 22, and 44. The default value is 8 kHz - // if your sound capture device supports this value. Otherwise, the default value is the next available capture level above 8 kHz that - // your sound capture device supports, usually 11 kHz. - m.rate = microRate; - - // see: http://www.adobe.com/cn/devnet/flashplayer/articles/acoustic-echo-cancellation.html - if (acodec.codec == "nellymoser") { - m.codec = SoundCodec.NELLYMOSER; - } else if (acodec.codec == "pcma") { - m.codec = SoundCodec.PCMA; - } else if (acodec.codec == "pcmu") { - m.codec = SoundCodec.PCMU; - } else { - m.codec = SoundCodec.SPEEX; - } - m.framesPerPacket = 1; - } - private function __build_video_codec(stream:NetStream, c:Camera, vcodec:Object):void { - if (!c) { - return; - } - - if(vcodec.codec == "vp6"){ - trace("use VP6, donot use H.264 publish encoding."); - return; - } - - var x264profile:String = (vcodec.profile == "main") ? H264Profile.MAIN : H264Profile.BASELINE; - var x264level:String = vcodec.level; - var cameraFps:Number = Number(vcodec.fps); - var x264KeyFrameInterval:int = int(vcodec.gop * cameraFps); - var cameraWidth:int = String(vcodec.size).split("x")[0]; - var cameraHeight:int = String(vcodec.size).split("x")[1]; - var cameraBitrate:int = int(vcodec.bitrate); - - // use default values. - var cameraQuality:int = 85; - - trace("[Publish] video h.264(x264) encoding parameters: " - + "profile=" + x264profile - + ", level=" + x264level - + ", keyFrameInterval(gop)=" + x264KeyFrameInterval - + "; video(camera) width=" + cameraWidth - + ", height=" + cameraHeight - + ", fps=" + cameraFps - + ", bitrate=" + cameraBitrate - + ", quality=" + cameraQuality - ); - - var h264Settings:H264VideoStreamSettings = new H264VideoStreamSettings(); - // we MUST set its values first, then set the NetStream.videoStreamSettings, or it will keep the origin values. - h264Settings.setProfileLevel(x264profile, x264level); - stream.videoStreamSettings = h264Settings; - // the setKeyFrameInterval/setMode/setQuality use the camera settings. - // http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/media/VideoStreamSettings.html - // Note This feature will be supported in future releases of Flash Player and AIR, for now, Camera parameters are used. - // - //h264Settings.setKeyFrameInterval(4); - //h264Settings.setMode(800, 600, 15); - //h264Settings.setQuality(500, 0); - - // set the camera and microphone. - - // setKeyFrameInterval(keyFrameInterval:int):void - // keyFrameInterval:int — A value that specifies which video frames are transmitted in full (as keyframes) instead of being - // interpolated by the video compression algorithm. A value of 1 means that every frame is a keyframe, a value of 3 means - // that every third frame is a keyframe, and so on. Acceptable values are 1 through 48. - c.setKeyFrameInterval(x264KeyFrameInterval); - - // setMode(width:int, height:int, fps:Number, favorArea:Boolean = true):void - // width:int — The requested capture width, in pixels. The default value is 160. - // height:int — The requested capture height, in pixels. The default value is 120. - // fps:Number — The requested rate at which the camera should capture data, in frames per second. The default value is 15. - c.setMode(cameraWidth, cameraHeight, cameraFps); - - // setQuality(bandwidth:int, quality:int):void - // bandwidth:int — Specifies the maximum amount of bandwidth that the current outgoing video feed can use, in bytes per second. - // To specify that the video can use as much bandwidth as needed to maintain the value of quality, pass 0 for bandwidth. - // The default value is 16384. - // quality:int — An integer that specifies the required level of picture quality, as determined by the amount of compression - // being applied to each video frame. Acceptable values range from 1 (lowest quality, maximum compression) to 100 - // (highest quality, no compression). To specify that picture quality can vary as needed to avoid exceeding bandwidth, - // pass 0 for quality. - // winlin: - // bandwidth is in Bps not kbps. - // quality=1 is lowest quality, 100 is highest quality. - c.setQuality(cameraBitrate / 8.0 * 1000, cameraQuality); - } - - private function __show_local_camera(c:Camera):void { - if (this.media_video) { - this.media_video.attachCamera(null); - this.removeChild(this.media_video); - this.media_video = null; - } - - // show local camera - media_video = new Video(); - media_video.attachCamera(c); - media_video.smoothing = true; - addChild(media_video); - - // rescale the local camera. - var cw:Number = user_h * c.width / c.height; - if (cw > user_w) { - var ch:Number = user_w * c.height / c.width; - media_video.width = user_w; - media_video.height = ch; - } else { - media_video.width = cw; - media_video.height = user_h; - } - media_video.x = (user_w - media_video.width) / 2; - media_video.y = (user_h - media_video.height) / 2; - - __draw_black_background(user_w, user_h); - - // lowest layer, for mask to cover it. - setChildIndex(media_video, 0); - } - - /** - * draw black background and draw the fullscreen mask. - */ - private function __draw_black_background(_width:int, _height:int):void { - // draw black bg. - this.graphics.beginFill(0x00, 1.0); - this.graphics.drawRect(0, 0, _width, _height); - this.graphics.endFill(); - - // draw the fs mask. - //this.control_fs_mask.graphics.beginFill(0xff0000, 0); - //this.control_fs_mask.graphics.drawRect(0, 0, _width, _height); - //this.control_fs_mask.graphics.endFill(); - } - } -} diff --git a/trunk/research/players/srs_reuse_conn/.actionScriptProperties b/trunk/research/players/srs_reuse_conn/.actionScriptProperties deleted file mode 100755 index ea33e282e..000000000 --- a/trunk/research/players/srs_reuse_conn/.actionScriptProperties +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/trunk/research/players/srs_reuse_conn/.project b/trunk/research/players/srs_reuse_conn/.project deleted file mode 100755 index 11dc3f9c2..000000000 --- a/trunk/research/players/srs_reuse_conn/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - srs_reuse_conn - - - - - - com.adobe.flexbuilder.project.flexbuilder - - - - - - com.adobe.flexbuilder.project.actionscriptnature - - diff --git a/trunk/research/players/srs_reuse_conn/.settings/org.eclipse.core.resources.prefs b/trunk/research/players/srs_reuse_conn/.settings/org.eclipse.core.resources.prefs deleted file mode 100644 index 0fc6bb311..000000000 --- a/trunk/research/players/srs_reuse_conn/.settings/org.eclipse.core.resources.prefs +++ /dev/null @@ -1,3 +0,0 @@ -#Sat Nov 22 18:40:22 CST 2014 -eclipse.preferences.version=1 -encoding/=utf-8 diff --git a/trunk/research/players/srs_reuse_conn/FlashCS5UI.swc b/trunk/research/players/srs_reuse_conn/FlashCS5UI.swc deleted file mode 100644 index ea19d9d5c559472563453f16d62aa627ffb02f7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 121293 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 diff --git a/trunk/research/players/srs_reuse_conn/release/srs_reuse_conn.swf b/trunk/research/players/srs_reuse_conn/release/srs_reuse_conn.swf deleted file mode 100644 index c072720169cf448a8e73c144f60155172505c58f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27452 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 LP0arSAZAdA6bDJv diff --git a/trunk/research/players/srs_reuse_conn/src/srs_reuse_conn.as b/trunk/research/players/srs_reuse_conn/src/srs_reuse_conn.as deleted file mode 100644 index 7516c5182..000000000 --- a/trunk/research/players/srs_reuse_conn/src/srs_reuse_conn.as +++ /dev/null @@ -1,123 +0,0 @@ -package -{ - import fl.controls.Button; - import fl.controls.TextInput; - - import flash.display.Sprite; - import flash.display.StageAlign; - import flash.display.StageScaleMode; - import flash.events.Event; - import flash.events.MouseEvent; - import flash.events.NetStatusEvent; - import flash.media.Video; - import flash.net.NetConnection; - import flash.net.NetStream; - - [SWF(backgroundColor="0xEEEEEE",frameRate="30",width="1024",height="576")] - public class srs_reuse_conn extends Sprite - { - public function srs_reuse_conn() - { - if (stage) { - onAddedToStage(null); - } else { - addEventListener(Event.ADDED_TO_STAGE, onAddedToStage); - } - } - - private function onAddedToStage(evt:Event):void - { - stage.align = StageAlign.TOP_LEFT; - stage.scaleMode = StageScaleMode.NO_SCALE; - - var txtUrl:TextInput = new TextInput(); - var btnConn:Button = new Button(); - var btnPlay:Button = new Button(); - - txtUrl.x = 10; - txtUrl.y = 10; - txtUrl.width = 400; - txtUrl.text = "rtmp://dev/live/livestream"; - addChild(txtUrl); - - btnConn.label = "Connect"; - btnConn.x = txtUrl.x + txtUrl.width + 10; - btnConn.y = txtUrl.y; - btnConn.width = 100; - addChild(btnConn); - - btnPlay.label = "Play"; - btnPlay.x = btnConn.x + btnConn.width + 10; - btnPlay.y = btnConn.y; - btnPlay.width = 100; - addChild(btnPlay); - - var video:Video = new Video(); - video.x = txtUrl.x; - video.y = txtUrl.y + txtUrl.height + 10; - addChild(video); - - var conn:NetConnection = null; - var stream:NetStream = null; - - var tcUrl:Function = function():String { - var url:String = txtUrl.text; - return url.substr(0, url.lastIndexOf("/")); - } - var streamName:Function = function():String { - var url:String = txtUrl.text; - return url.substr(tcUrl().length + 1); - } - - var closeConnection:Function = function():void { - if (stream) { - stream.close(); - stream = null; - } - if (conn) { - conn.close(); - conn = null; - } - btnConn.label = "Connect"; - btnPlay.visible = false; - }; - - btnPlay.visible = false; - btnConn.addEventListener(MouseEvent.CLICK, function(e:MouseEvent):void { - if (btnConn.label == "Connect") { - conn = new NetConnection(); - conn.client = { - onBWDone: function():void{} - }; - conn.addEventListener(NetStatusEvent.NET_STATUS, function(ne:NetStatusEvent):void { - if (ne.info.code == "NetConnection.Connect.Success") { - btnPlay.visible = true; - } else if (ne.info.code == "NetConnection.Connect.Closed") { - closeConnection(); - } - trace(ne.info.code); - }); - conn.connect(tcUrl()); - btnConn.label = "Close"; - } else { - closeConnection(); - } - }); - btnPlay.addEventListener(MouseEvent.CLICK, function(e:MouseEvent):void { - if (stream) { - stream.close(); - stream = null; - } - stream = new NetStream(conn); - stream.client = { - onMetaData: function(metadata:Object):void { - video.width = metadata.width; - video.height = metadata.height; - } - }; - video.attachNetStream(stream); - stream.play(streamName()); - }); - } - } -} diff --git a/trunk/src/core/srs_core_version5.hpp b/trunk/src/core/srs_core_version5.hpp index ac4ae77dd..b27c5f28f 100644 --- a/trunk/src/core/srs_core_version5.hpp +++ b/trunk/src/core/srs_core_version5.hpp @@ -9,6 +9,6 @@ #define VERSION_MAJOR 5 #define VERSION_MINOR 0 -#define VERSION_REVISION 21 +#define VERSION_REVISION 22 #endif