diff --git a/src/JT1078.FMp4.Test/H264/.vscode/settings.json b/src/JT1078.FMp4.Test/H264/.vscode/settings.json new file mode 100644 index 0000000..6f3a291 --- /dev/null +++ b/src/JT1078.FMp4.Test/H264/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "liveServer.settings.port": 5501 +} \ No newline at end of file diff --git a/src/JT1078.FMp4.Test/H264/index.html b/src/JT1078.FMp4.Test/H264/index.html index 57ef48a..4f25200 100644 --- a/src/JT1078.FMp4.Test/H264/index.html +++ b/src/JT1078.FMp4.Test/H264/index.html @@ -3,47 +3,202 @@ - fmp4 demo + WebSocket MSE Fmp4 demo +

MSE FMp4 Demo

- + + \ No newline at end of file diff --git a/src/JT1078.FMp4.Test/H264/signalr.min.js b/src/JT1078.FMp4.Test/H264/signalr.min.js new file mode 100644 index 0000000..d37bd31 --- /dev/null +++ b/src/JT1078.FMp4.Test/H264/signalr.min.js @@ -0,0 +1,17 @@ +(function webpackUniversalModuleDefinition(root,factory){if(typeof exports==="object"&&typeof module==="object")module.exports=factory();else if(typeof define==="function"&&define.amd)define([],factory);else if(typeof exports==="object")exports["signalR"]=factory();else root["signalR"]=factory()})(window,function(){return function(modules){var installedModules={};function __webpack_require__(moduleId){if(installedModules[moduleId]){return installedModules[moduleId].exports}var module=installedModules[moduleId]={i:moduleId,l:false,exports:{}};modules[moduleId].call(module.exports,module,module.exports,__webpack_require__);module.l=true;return module.exports}__webpack_require__.m=modules;__webpack_require__.c=installedModules;__webpack_require__.d=function(exports,name,getter){if(!__webpack_require__.o(exports,name)){Object.defineProperty(exports,name,{enumerable:true,get:getter})}};__webpack_require__.r=function(exports){if(typeof Symbol!=="undefined"&&Symbol.toStringTag){Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"})}Object.defineProperty(exports,"__esModule",{value:true})};__webpack_require__.t=function(value,mode){if(mode&1)value=__webpack_require__(value);if(mode&8)return value;if(mode&4&&typeof value==="object"&&value&&value.__esModule)return value;var ns=Object.create(null);__webpack_require__.r(ns);Object.defineProperty(ns,"default",{enumerable:true,value:value});if(mode&2&&typeof value!="string")for(var key in value)__webpack_require__.d(ns,key,function(key){return value[key]}.bind(null,key));return ns};__webpack_require__.n=function(module){var getter=module&&module.__esModule?function getDefault(){return module["default"]}:function getModuleExports(){return module};__webpack_require__.d(getter,"a",getter);return getter};__webpack_require__.o=function(object,property){return Object.prototype.hasOwnProperty.call(object,property)};__webpack_require__.p="";return __webpack_require__(__webpack_require__.s=0)}([function(module,__webpack_exports__,__webpack_require__){"use strict";__webpack_require__.r(__webpack_exports__);var es6_promise_dist_es6_promise_auto_js__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__(1);var es6_promise_dist_es6_promise_auto_js__WEBPACK_IMPORTED_MODULE_0___default=__webpack_require__.n(es6_promise_dist_es6_promise_auto_js__WEBPACK_IMPORTED_MODULE_0__);var _index__WEBPACK_IMPORTED_MODULE_1__=__webpack_require__(3);__webpack_require__.d(__webpack_exports__,"VERSION",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["VERSION"]});__webpack_require__.d(__webpack_exports__,"AbortError",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["AbortError"]});__webpack_require__.d(__webpack_exports__,"HttpError",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["HttpError"]});__webpack_require__.d(__webpack_exports__,"TimeoutError",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["TimeoutError"]});__webpack_require__.d(__webpack_exports__,"HttpClient",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["HttpClient"]});__webpack_require__.d(__webpack_exports__,"HttpResponse",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["HttpResponse"]});__webpack_require__.d(__webpack_exports__,"DefaultHttpClient",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["DefaultHttpClient"]});__webpack_require__.d(__webpack_exports__,"HubConnection",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["HubConnection"]});__webpack_require__.d(__webpack_exports__,"HubConnectionState",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["HubConnectionState"]});__webpack_require__.d(__webpack_exports__,"HubConnectionBuilder",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["HubConnectionBuilder"]});__webpack_require__.d(__webpack_exports__,"MessageType",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["MessageType"]});__webpack_require__.d(__webpack_exports__,"LogLevel",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["LogLevel"]});__webpack_require__.d(__webpack_exports__,"HttpTransportType",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["HttpTransportType"]});__webpack_require__.d(__webpack_exports__,"TransferFormat",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["TransferFormat"]});__webpack_require__.d(__webpack_exports__,"NullLogger",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["NullLogger"]});__webpack_require__.d(__webpack_exports__,"JsonHubProtocol",function(){return _index__WEBPACK_IMPORTED_MODULE_1__["JsonHubProtocol"]});if(!Uint8Array.prototype.indexOf){Object.defineProperty(Uint8Array.prototype,"indexOf",{value:Array.prototype.indexOf,writable:true})}if(!Uint8Array.prototype.slice){Object.defineProperty(Uint8Array.prototype,"slice",{value:Array.prototype.slice,writable:true})}if(!Uint8Array.prototype.forEach){Object.defineProperty(Uint8Array.prototype,"forEach",{value:Array.prototype.forEach,writable:true})}},function(module,exports,__webpack_require__){(function(global){var require; +/*! + * @overview es6-promise - a tiny implementation of Promises/A+. + * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald) + * @license Licensed under MIT license + * See https://raw.githubusercontent.com/stefanpenner/es6-promise/master/LICENSE + * @version v4.2.2+97478eb6 + */ +/*! + * @overview es6-promise - a tiny implementation of Promises/A+. + * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald) + * @license Licensed under MIT license + * See https://raw.githubusercontent.com/stefanpenner/es6-promise/master/LICENSE + * @version v4.2.2+97478eb6 + */ +(function(global,factory){true?module.exports=factory():undefined})(this,function(){"use strict";function objectOrFunction(x){var type=typeof x;return x!==null&&(type==="object"||type==="function")}function isFunction(x){return typeof x==="function"}var _isArray=void 0;if(Array.isArray){_isArray=Array.isArray}else{_isArray=function(x){return Object.prototype.toString.call(x)==="[object Array]"}}var isArray=_isArray;var len=0;var vertxNext=void 0;var customSchedulerFn=void 0;var asap=function asap(callback,arg){queue[len]=callback;queue[len+1]=arg;len+=2;if(len===2){if(customSchedulerFn){customSchedulerFn(flush)}else{scheduleFlush()}}};function setScheduler(scheduleFn){customSchedulerFn=scheduleFn}function setAsap(asapFn){asap=asapFn}var browserWindow=typeof window!=="undefined"?window:undefined;var browserGlobal=browserWindow||{};var BrowserMutationObserver=browserGlobal.MutationObserver||browserGlobal.WebKitMutationObserver;var isNode=typeof self==="undefined"&&typeof process!=="undefined"&&{}.toString.call(process)==="[object process]";var isWorker=typeof Uint8ClampedArray!=="undefined"&&typeof importScripts!=="undefined"&&typeof MessageChannel!=="undefined";function useNextTick(){return function(){return process.nextTick(flush)}}function useVertxTimer(){if(typeof vertxNext!=="undefined"){return function(){vertxNext(flush)}}return useSetTimeout()}function useMutationObserver(){var iterations=0;var observer=new BrowserMutationObserver(flush);var node=document.createTextNode("");observer.observe(node,{characterData:true});return function(){node.data=iterations=++iterations%2}}function useMessageChannel(){var channel=new MessageChannel;channel.port1.onmessage=flush;return function(){return channel.port2.postMessage(0)}}function useSetTimeout(){var globalSetTimeout=setTimeout;return function(){return globalSetTimeout(flush,1)}}var queue=new Array(1e3);function flush(){for(var i=0;i=200&&xhr.status<300){resolve(new _HttpClient__WEBPACK_IMPORTED_MODULE_1__["HttpResponse"](xhr.status,xhr.statusText,xhr.response||xhr.responseText))}else{reject(new _Errors__WEBPACK_IMPORTED_MODULE_0__["HttpError"](xhr.statusText,xhr.status))}};xhr.onerror=function(){_this.logger.log(_ILogger__WEBPACK_IMPORTED_MODULE_2__["LogLevel"].Warning,"Error from HTTP request. "+xhr.status+": "+xhr.statusText+".");reject(new _Errors__WEBPACK_IMPORTED_MODULE_0__["HttpError"](xhr.statusText,xhr.status))};xhr.ontimeout=function(){_this.logger.log(_ILogger__WEBPACK_IMPORTED_MODULE_2__["LogLevel"].Warning,"Timeout from HTTP request.");reject(new _Errors__WEBPACK_IMPORTED_MODULE_0__["TimeoutError"])};xhr.send(request.content||"")})};return XhrHttpClient}(_HttpClient__WEBPACK_IMPORTED_MODULE_1__["HttpClient"])},function(module,__webpack_exports__,__webpack_require__){"use strict";__webpack_require__.r(__webpack_exports__);__webpack_require__.d(__webpack_exports__,"LogLevel",function(){return LogLevel});var LogLevel;(function(LogLevel){LogLevel[LogLevel["Trace"]=0]="Trace";LogLevel[LogLevel["Debug"]=1]="Debug";LogLevel[LogLevel["Information"]=2]="Information";LogLevel[LogLevel["Warning"]=3]="Warning";LogLevel[LogLevel["Error"]=4]="Error";LogLevel[LogLevel["Critical"]=5]="Critical";LogLevel[LogLevel["None"]=6]="None"})(LogLevel||(LogLevel={}))},function(module,__webpack_exports__,__webpack_require__){"use strict";__webpack_require__.r(__webpack_exports__);__webpack_require__.d(__webpack_exports__,"HubConnectionState",function(){return HubConnectionState});__webpack_require__.d(__webpack_exports__,"HubConnection",function(){return HubConnection});var _HandshakeProtocol__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__(11);var _IHubProtocol__WEBPACK_IMPORTED_MODULE_1__=__webpack_require__(15);var _ILogger__WEBPACK_IMPORTED_MODULE_2__=__webpack_require__(9);var _Utils__WEBPACK_IMPORTED_MODULE_3__=__webpack_require__(13);var __awaiter=undefined&&undefined.__awaiter||function(thisArg,_arguments,P,generator){return new(P||(P=Promise))(function(resolve,reject){function fulfilled(value){try{step(generator.next(value))}catch(e){reject(e)}}function rejected(value){try{step(generator["throw"](value))}catch(e){reject(e)}}function step(result){result.done?resolve(result.value):new P(function(resolve){resolve(result.value)}).then(fulfilled,rejected)}step((generator=generator.apply(thisArg,_arguments||[])).next())})};var __generator=undefined&&undefined.__generator||function(thisArg,body){var _={label:0,sent:function(){if(t[0]&1)throw t[1];return t[1]},trys:[],ops:[]},f,y,t,g;return g={next:verb(0),throw:verb(1),return:verb(2)},typeof Symbol==="function"&&(g[Symbol.iterator]=function(){return this}),g;function verb(n){return function(v){return step([n,v])}}function step(op){if(f)throw new TypeError("Generator is already executing.");while(_)try{if(f=1,y&&(t=op[0]&2?y["return"]:op[0]?y["throw"]||((t=y["return"])&&t.call(y),0):y.next)&&!(t=t.call(y,op[1])).done)return t;if(y=0,t)op=[op[0]&2,t.value];switch(op[0]){case 0:case 1:t=op;break;case 4:_.label++;return{value:op[1],done:false};case 5:_.label++;y=op[1];op=[0];continue;case 7:op=_.ops.pop();_.trys.pop();continue;default:if(!(t=_.trys,t=t.length>0&&t[t.length-1])&&(op[0]===6||op[0]===2)){_=0;continue}if(op[0]===3&&(!t||op[1]>t[0]&&op[1]responseLength?binaryData.slice(responseLength).buffer:null}else{var textData=data;var separatorIndex=textData.indexOf(_TextMessageFormat__WEBPACK_IMPORTED_MODULE_0__["TextMessageFormat"].RecordSeparator);if(separatorIndex===-1){throw new Error("Message is incomplete.")}var responseLength=separatorIndex+1;messageData=textData.substring(0,responseLength);remainingData=textData.length>responseLength?textData.substring(responseLength):null}var messages=_TextMessageFormat__WEBPACK_IMPORTED_MODULE_0__["TextMessageFormat"].parse(messageData);var response=JSON.parse(messages[0]);if(response.type){throw new Error("Expected a handshake response from the server.")}responseMessage=response;return[remainingData,responseMessage]};return HandshakeProtocol}()},function(module,__webpack_exports__,__webpack_require__){"use strict";__webpack_require__.r(__webpack_exports__);__webpack_require__.d(__webpack_exports__,"TextMessageFormat",function(){return TextMessageFormat});var TextMessageFormat=function(){function TextMessageFormat(){}TextMessageFormat.write=function(output){return""+output+TextMessageFormat.RecordSeparator};TextMessageFormat.parse=function(input){if(input[input.length-1]!==TextMessageFormat.RecordSeparator){throw new Error("Message is incomplete.")}var messages=input.split(TextMessageFormat.RecordSeparator);messages.pop();return messages};TextMessageFormat.RecordSeparatorCode=30;TextMessageFormat.RecordSeparator=String.fromCharCode(TextMessageFormat.RecordSeparatorCode);return TextMessageFormat}()},function(module,__webpack_exports__,__webpack_require__){"use strict";__webpack_require__.r(__webpack_exports__);__webpack_require__.d(__webpack_exports__,"Arg",function(){return Arg});__webpack_require__.d(__webpack_exports__,"getDataDetail",function(){return getDataDetail});__webpack_require__.d(__webpack_exports__,"formatArrayBuffer",function(){return formatArrayBuffer});__webpack_require__.d(__webpack_exports__,"isArrayBuffer",function(){return isArrayBuffer});__webpack_require__.d(__webpack_exports__,"sendMessage",function(){return sendMessage});__webpack_require__.d(__webpack_exports__,"createLogger",function(){return createLogger});__webpack_require__.d(__webpack_exports__,"Subject",function(){return Subject});__webpack_require__.d(__webpack_exports__,"SubjectSubscription",function(){return SubjectSubscription});__webpack_require__.d(__webpack_exports__,"ConsoleLogger",function(){return ConsoleLogger});var _ILogger__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__(9);var _Loggers__WEBPACK_IMPORTED_MODULE_1__=__webpack_require__(14);var __awaiter=undefined&&undefined.__awaiter||function(thisArg,_arguments,P,generator){return new(P||(P=Promise))(function(resolve,reject){function fulfilled(value){try{step(generator.next(value))}catch(e){reject(e)}}function rejected(value){try{step(generator["throw"](value))}catch(e){reject(e)}}function step(result){result.done?resolve(result.value):new P(function(resolve){resolve(result.value)}).then(fulfilled,rejected)}step((generator=generator.apply(thisArg,_arguments||[])).next())})};var __generator=undefined&&undefined.__generator||function(thisArg,body){var _={label:0,sent:function(){if(t[0]&1)throw t[1];return t[1]},trys:[],ops:[]},f,y,t,g;return g={next:verb(0),throw:verb(1),return:verb(2)},typeof Symbol==="function"&&(g[Symbol.iterator]=function(){return this}),g;function verb(n){return function(v){return step([n,v])}}function step(op){if(f)throw new TypeError("Generator is already executing.");while(_)try{if(f=1,y&&(t=op[0]&2?y["return"]:op[0]?y["throw"]||((t=y["return"])&&t.call(y),0):y.next)&&!(t=t.call(y,op[1])).done)return t;if(y=0,t)op=[op[0]&2,t.value];switch(op[0]){case 0:case 1:t=op;break;case 4:_.label++;return{value:op[1],done:false};case 5:_.label++;y=op[1];op=[0];continue;case 7:op=_.ops.pop();_.trys.pop();continue;default:if(!(t=_.trys,t=t.length>0&&t[t.length-1])&&(op[0]===6||op[0]===2)){_=0;continue}if(op[0]===3&&(!t||op[1]>t[0]&&op[1]-1){this.subject.observers.splice(index,1)}if(this.subject.observers.length===0&&this.subject.cancelCallback){this.subject.cancelCallback().catch(function(_){})}};return SubjectSubscription}();var ConsoleLogger=function(){function ConsoleLogger(minimumLogLevel){this.minimumLogLevel=minimumLogLevel}ConsoleLogger.prototype.log=function(logLevel,message){if(logLevel>=this.minimumLogLevel){switch(logLevel){case _ILogger__WEBPACK_IMPORTED_MODULE_0__["LogLevel"].Critical:case _ILogger__WEBPACK_IMPORTED_MODULE_0__["LogLevel"].Error:console.error("["+(new Date).toISOString()+"] "+_ILogger__WEBPACK_IMPORTED_MODULE_0__["LogLevel"][logLevel]+": "+message);break;case _ILogger__WEBPACK_IMPORTED_MODULE_0__["LogLevel"].Warning:console.warn("["+(new Date).toISOString()+"] "+_ILogger__WEBPACK_IMPORTED_MODULE_0__["LogLevel"][logLevel]+": "+message);break;case _ILogger__WEBPACK_IMPORTED_MODULE_0__["LogLevel"].Information:console.info("["+(new Date).toISOString()+"] "+_ILogger__WEBPACK_IMPORTED_MODULE_0__["LogLevel"][logLevel]+": "+message);break;default:console.log("["+(new Date).toISOString()+"] "+_ILogger__WEBPACK_IMPORTED_MODULE_0__["LogLevel"][logLevel]+": "+message);break}}};return ConsoleLogger}()},function(module,__webpack_exports__,__webpack_require__){"use strict";__webpack_require__.r(__webpack_exports__);__webpack_require__.d(__webpack_exports__,"NullLogger",function(){return NullLogger});var NullLogger=function(){function NullLogger(){}NullLogger.prototype.log=function(_logLevel,_message){};NullLogger.instance=new NullLogger;return NullLogger}()},function(module,__webpack_exports__,__webpack_require__){"use strict";__webpack_require__.r(__webpack_exports__);__webpack_require__.d(__webpack_exports__,"MessageType",function(){return MessageType});var MessageType;(function(MessageType){MessageType[MessageType["Invocation"]=1]="Invocation";MessageType[MessageType["StreamItem"]=2]="StreamItem";MessageType[MessageType["Completion"]=3]="Completion";MessageType[MessageType["StreamInvocation"]=4]="StreamInvocation";MessageType[MessageType["CancelInvocation"]=5]="CancelInvocation";MessageType[MessageType["Ping"]=6]="Ping";MessageType[MessageType["Close"]=7]="Close"})(MessageType||(MessageType={}))},function(module,__webpack_exports__,__webpack_require__){"use strict";__webpack_require__.r(__webpack_exports__);__webpack_require__.d(__webpack_exports__,"HubConnectionBuilder",function(){return HubConnectionBuilder});var _HttpConnection__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__(17);var _HubConnection__WEBPACK_IMPORTED_MODULE_1__=__webpack_require__(10);var _JsonHubProtocol__WEBPACK_IMPORTED_MODULE_2__=__webpack_require__(23);var _Loggers__WEBPACK_IMPORTED_MODULE_3__=__webpack_require__(14);var _Utils__WEBPACK_IMPORTED_MODULE_4__=__webpack_require__(13);var HubConnectionBuilder=function(){function HubConnectionBuilder(){}HubConnectionBuilder.prototype.configureLogging=function(logging){_Utils__WEBPACK_IMPORTED_MODULE_4__["Arg"].isRequired(logging,"logging");if(isLogger(logging)){this.logger=logging}else{this.logger=new _Utils__WEBPACK_IMPORTED_MODULE_4__["ConsoleLogger"](logging)}return this};HubConnectionBuilder.prototype.withUrl=function(url,transportTypeOrOptions){_Utils__WEBPACK_IMPORTED_MODULE_4__["Arg"].isRequired(url,"url");this.url=url;if(typeof transportTypeOrOptions==="object"){this.httpConnectionOptions=transportTypeOrOptions}else{this.httpConnectionOptions={transport:transportTypeOrOptions}}return this};HubConnectionBuilder.prototype.withHubProtocol=function(protocol){_Utils__WEBPACK_IMPORTED_MODULE_4__["Arg"].isRequired(protocol,"protocol");this.protocol=protocol;return this};HubConnectionBuilder.prototype.build=function(){var httpConnectionOptions=this.httpConnectionOptions||{};if(httpConnectionOptions.logger===undefined){httpConnectionOptions.logger=this.logger}if(!this.url){throw new Error("The 'HubConnectionBuilder.withUrl' method must be called before building the connection.")}var connection=new _HttpConnection__WEBPACK_IMPORTED_MODULE_0__["HttpConnection"](this.url,httpConnectionOptions);return _HubConnection__WEBPACK_IMPORTED_MODULE_1__["HubConnection"].create(connection,this.logger||_Loggers__WEBPACK_IMPORTED_MODULE_3__["NullLogger"].instance,this.protocol||new _JsonHubProtocol__WEBPACK_IMPORTED_MODULE_2__["JsonHubProtocol"])};return HubConnectionBuilder}();function isLogger(logger){return logger.log!==undefined}},function(module,__webpack_exports__,__webpack_require__){"use strict";__webpack_require__.r(__webpack_exports__);__webpack_require__.d(__webpack_exports__,"HttpConnection",function(){return HttpConnection});var _DefaultHttpClient__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__(6);var _ILogger__WEBPACK_IMPORTED_MODULE_1__=__webpack_require__(9);var _ITransport__WEBPACK_IMPORTED_MODULE_2__=__webpack_require__(18);var _LongPollingTransport__WEBPACK_IMPORTED_MODULE_3__=__webpack_require__(19);var _ServerSentEventsTransport__WEBPACK_IMPORTED_MODULE_4__=__webpack_require__(21);var _Utils__WEBPACK_IMPORTED_MODULE_5__=__webpack_require__(13);var _WebSocketTransport__WEBPACK_IMPORTED_MODULE_6__=__webpack_require__(22);var __awaiter=undefined&&undefined.__awaiter||function(thisArg,_arguments,P,generator){return new(P||(P=Promise))(function(resolve,reject){function fulfilled(value){try{step(generator.next(value))}catch(e){reject(e)}}function rejected(value){try{step(generator["throw"](value))}catch(e){reject(e)}}function step(result){result.done?resolve(result.value):new P(function(resolve){resolve(result.value)}).then(fulfilled,rejected)}step((generator=generator.apply(thisArg,_arguments||[])).next())})};var __generator=undefined&&undefined.__generator||function(thisArg,body){var _={label:0,sent:function(){if(t[0]&1)throw t[1];return t[1]},trys:[],ops:[]},f,y,t,g;return g={next:verb(0),throw:verb(1),return:verb(2)},typeof Symbol==="function"&&(g[Symbol.iterator]=function(){return this}),g;function verb(n){return function(v){return step([n,v])}}function step(op){if(f)throw new TypeError("Generator is already executing.");while(_)try{if(f=1,y&&(t=op[0]&2?y["return"]:op[0]?y["throw"]||((t=y["return"])&&t.call(y),0):y.next)&&!(t=t.call(y,op[1])).done)return t;if(y=0,t)op=[op[0]&2,t.value];switch(op[0]){case 0:case 1:t=op;break;case 4:_.label++;return{value:op[1],done:false};case 5:_.label++;y=op[1];op=[0];continue;case 7:op=_.ops.pop();_.trys.pop();continue;default:if(!(t=_.trys,t=t.length>0&&t[t.length-1])&&(op[0]===6||op[0]===2)){_=0;continue}if(op[0]===3&&(!t||op[1]>t[0]&&op[1]=0){if(transport===_ITransport__WEBPACK_IMPORTED_MODULE_2__["HttpTransportType"].WebSockets&&!this.options.WebSocket||transport===_ITransport__WEBPACK_IMPORTED_MODULE_2__["HttpTransportType"].ServerSentEvents&&!this.options.EventSource){this.logger.log(_ILogger__WEBPACK_IMPORTED_MODULE_1__["LogLevel"].Debug,"Skipping transport '"+_ITransport__WEBPACK_IMPORTED_MODULE_2__["HttpTransportType"][transport]+"' because it is not supported in your environment.'")}else{this.logger.log(_ILogger__WEBPACK_IMPORTED_MODULE_1__["LogLevel"].Debug,"Selecting transport '"+_ITransport__WEBPACK_IMPORTED_MODULE_2__["HttpTransportType"][transport]+"'.");return transport}}else{this.logger.log(_ILogger__WEBPACK_IMPORTED_MODULE_1__["LogLevel"].Debug,"Skipping transport '"+_ITransport__WEBPACK_IMPORTED_MODULE_2__["HttpTransportType"][transport]+"' because it does not support the requested transfer format '"+_ITransport__WEBPACK_IMPORTED_MODULE_2__["TransferFormat"][requestedTransferFormat]+"'.")}}else{this.logger.log(_ILogger__WEBPACK_IMPORTED_MODULE_1__["LogLevel"].Debug,"Skipping transport '"+_ITransport__WEBPACK_IMPORTED_MODULE_2__["HttpTransportType"][transport]+"' because it was disabled by the client.")}}return null};HttpConnection.prototype.isITransport=function(transport){return transport&&typeof transport==="object"&&"connect"in transport};HttpConnection.prototype.changeState=function(from,to){if(this.connectionState===from){this.connectionState=to;return true}return false};HttpConnection.prototype.stopConnection=function(error){this.transport=undefined;error=this.stopError||error;if(error){this.logger.log(_ILogger__WEBPACK_IMPORTED_MODULE_1__["LogLevel"].Error,"Connection disconnected with error '"+error+"'.")}else{this.logger.log(_ILogger__WEBPACK_IMPORTED_MODULE_1__["LogLevel"].Information,"Connection disconnected.")}this.connectionState=2;if(this.onclose){this.onclose(error)}};HttpConnection.prototype.resolveUrl=function(url){if(url.lastIndexOf("https://",0)===0||url.lastIndexOf("http://",0)===0){return url}if(typeof window==="undefined"||!window||!window.document){throw new Error("Cannot resolve '"+url+"'.")}var aTag=window.document.createElement("a");aTag.href=url;this.logger.log(_ILogger__WEBPACK_IMPORTED_MODULE_1__["LogLevel"].Information,"Normalizing '"+url+"' to '"+aTag.href+"'.");return aTag.href};HttpConnection.prototype.resolveNegotiateUrl=function(url){var index=url.indexOf("?");var negotiateUrl=url.substring(0,index===-1?url.length:index);if(negotiateUrl[negotiateUrl.length-1]!=="/"){negotiateUrl+="/"}negotiateUrl+="negotiate";negotiateUrl+=index===-1?"":url.substring(index);return negotiateUrl};return HttpConnection}();function transportMatches(requestedTransport,actualTransport){return!requestedTransport||(actualTransport&requestedTransport)!==0}},function(module,__webpack_exports__,__webpack_require__){"use strict";__webpack_require__.r(__webpack_exports__);__webpack_require__.d(__webpack_exports__,"HttpTransportType",function(){return HttpTransportType});__webpack_require__.d(__webpack_exports__,"TransferFormat",function(){return TransferFormat});var HttpTransportType;(function(HttpTransportType){HttpTransportType[HttpTransportType["None"]=0]="None";HttpTransportType[HttpTransportType["WebSockets"]=1]="WebSockets";HttpTransportType[HttpTransportType["ServerSentEvents"]=2]="ServerSentEvents";HttpTransportType[HttpTransportType["LongPolling"]=4]="LongPolling"})(HttpTransportType||(HttpTransportType={}));var TransferFormat;(function(TransferFormat){TransferFormat[TransferFormat["Text"]=1]="Text";TransferFormat[TransferFormat["Binary"]=2]="Binary"})(TransferFormat||(TransferFormat={}))},function(module,__webpack_exports__,__webpack_require__){"use strict";__webpack_require__.r(__webpack_exports__);__webpack_require__.d(__webpack_exports__,"LongPollingTransport",function(){return LongPollingTransport});var _AbortController__WEBPACK_IMPORTED_MODULE_0__=__webpack_require__(20);var _Errors__WEBPACK_IMPORTED_MODULE_1__=__webpack_require__(4);var _ILogger__WEBPACK_IMPORTED_MODULE_2__=__webpack_require__(9);var _ITransport__WEBPACK_IMPORTED_MODULE_3__=__webpack_require__(18);var _Utils__WEBPACK_IMPORTED_MODULE_4__=__webpack_require__(13);var __awaiter=undefined&&undefined.__awaiter||function(thisArg,_arguments,P,generator){return new(P||(P=Promise))(function(resolve,reject){function fulfilled(value){try{step(generator.next(value))}catch(e){reject(e)}}function rejected(value){try{step(generator["throw"](value))}catch(e){reject(e)}}function step(result){result.done?resolve(result.value):new P(function(resolve){resolve(result.value)}).then(fulfilled,rejected)}step((generator=generator.apply(thisArg,_arguments||[])).next())})};var __generator=undefined&&undefined.__generator||function(thisArg,body){var _={label:0,sent:function(){if(t[0]&1)throw t[1];return t[1]},trys:[],ops:[]},f,y,t,g;return g={next:verb(0),throw:verb(1),return:verb(2)},typeof Symbol==="function"&&(g[Symbol.iterator]=function(){return this}),g;function verb(n){return function(v){return step([n,v])}}function step(op){if(f)throw new TypeError("Generator is already executing.");while(_)try{if(f=1,y&&(t=op[0]&2?y["return"]:op[0]?y["throw"]||((t=y["return"])&&t.call(y),0):y.next)&&!(t=t.call(y,op[1])).done)return t;if(y=0,t)op=[op[0]&2,t.value];switch(op[0]){case 0:case 1:t=op;break;case 4:_.label++;return{value:op[1],done:false};case 5:_.label++;y=op[1];op=[0];continue;case 7:op=_.ops.pop();_.trys.pop();continue;default:if(!(t=_.trys,t=t.length>0&&t[t.length-1])&&(op[0]===6||op[0]===2)){_=0;continue}if(op[0]===3&&(!t||op[1]>t[0]&&op[1]0&&t[t.length-1])&&(op[0]===6||op[0]===2)){_=0;continue}if(op[0]===3&&(!t||op[1]>t[0]&&op[1]0&&t[t.length-1])&&(op[0]===6||op[0]===2)){_=0;continue}if(op[0]===3&&(!t||op[1]>t[0]&&op[1] s.RawData).ToList(); moofs.Add(fragmentBox); - foreach(var moof in moofs) + foreach (var moof in moofs) { moof.ToBuffer(ref writer); } @@ -437,6 +437,7 @@ namespace JT1078.FMp4.Test public void Test4() { FMp4Encoder fMp4Encoder = new FMp4Encoder(); + H264Decoder h264Decoder = new H264Decoder(); var packages = ParseNALUTests(); var filepath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_4.mp4"); if (File.Exists(filepath)) @@ -444,15 +445,20 @@ namespace JT1078.FMp4.Test File.Delete(filepath); } using var fileStream = new FileStream(filepath, FileMode.OpenOrCreate, FileAccess.Write); + var ftyp = fMp4Encoder.EncoderFtypBox(); + fileStream.Write(ftyp); var package1 = packages[0]; - var buffer1 = fMp4Encoder.EncoderFirstVideoBox(package1); - fileStream.Write(buffer1); - int moofOffset = buffer1.Length; - foreach (var package in packages.Take(2)) + var nalus1 = h264Decoder.ParseNALU(package1); + var moov = fMp4Encoder.EncoderMoovBox(nalus1, package1.Bodies.Length); + fileStream.Write(moov); + int moofOffset = ftyp.Length + moov.Length; + var flag = package1.Label3.DataType == Protocol.Enums.JT1078DataType.视频I帧 ? 1u : 0u; + var otherMoofBuffer = fMp4Encoder.EncoderMoofBox(nalus1, package1.Bodies.Length, package1.Timestamp, flag); + foreach (var package in packages) { - var otherBuffer = fMp4Encoder.EncoderOtherVideoBox(package, (ulong)moofOffset); - moofOffset += otherBuffer.Length; - fileStream.Write(otherBuffer); + var otherNalus = h264Decoder.ParseNALU(package); + var otherMdatBuffer = fMp4Encoder.EncoderMdatBox(otherNalus, package.Bodies.Length); + fileStream.Write(otherMdatBuffer); } fileStream.Close(); } @@ -464,7 +470,7 @@ namespace JT1078.FMp4.Test //01 20 00 00 var a = BinaryPrimitives.ReadUInt32LittleEndian(new byte[] { 0x01, 0x60, 0, 0 }); var b = BinaryPrimitives.ReadUInt32LittleEndian(new byte[] { 0x01, 0x20, 0, 0 }); - + //00 00 01 60 //00 00 01 20 var c = BinaryPrimitives.ReadUInt32BigEndian(new byte[] { 0, 0, 0x01, 0x20 }); @@ -495,15 +501,15 @@ namespace JT1078.FMp4.Test { var filepath1 = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "JT1078_1.mp4"); var filepath2 = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "jt1078_1_fragmented.mp4"); - var byte1=File.ReadAllBytes(filepath1); - var byte2=File.ReadAllBytes(filepath2); - if(byte1.Length== byte2.Length) + var byte1 = File.ReadAllBytes(filepath1); + var byte2 = File.ReadAllBytes(filepath2); + if (byte1.Length == byte2.Length) { - for(var i=0;i< byte1.Length; i++) + for (var i = 0; i < byte1.Length; i++) { if (byte1[i] != byte2[i]) { - + } } } diff --git a/src/JT1078.FMp4/Boxs/MovieBox.cs b/src/JT1078.FMp4/Boxs/MovieBox.cs index 231fbb7..4c923f5 100644 --- a/src/JT1078.FMp4/Boxs/MovieBox.cs +++ b/src/JT1078.FMp4/Boxs/MovieBox.cs @@ -40,7 +40,10 @@ namespace JT1078.FMp4 Start(ref writer); MovieHeaderBox.ToBuffer(ref writer); TrackBox.ToBuffer(ref writer); - MovieExtendsBox.ToBuffer(ref writer); + if (MovieExtendsBox != null) + { + MovieExtendsBox.ToBuffer(ref writer); + } if (UserDataBox != null) { UserDataBox.ToBuffer(ref writer); diff --git a/src/JT1078.FMp4/FMp4Encoder.cs b/src/JT1078.FMp4/FMp4Encoder.cs index da5cd54..2a149dd 100644 --- a/src/JT1078.FMp4/FMp4Encoder.cs +++ b/src/JT1078.FMp4/FMp4Encoder.cs @@ -2,6 +2,7 @@ using JT1078.FMp4.MessagePack; using JT1078.FMp4.Samples; using JT1078.Protocol; +using JT1078.Protocol.Enums; using JT1078.Protocol.H264; using JT1078.Protocol.MessagePack; using System; @@ -23,6 +24,7 @@ namespace JT1078.FMp4 /// moof n /// mdat n /// mfra + /// ref: https://www.w3.org/TR/mse-byte-stream-format-isobmff/#movie-fragment-relative-addressing /// public class FMp4Encoder { @@ -35,6 +37,233 @@ namespace JT1078.FMp4 h264Decoder = new H264Decoder(); } + + /// + /// 编码ftyp盒子 + /// + /// + public byte[] EncoderFtypBox() + { + byte[] buffer = FMp4ArrayPool.Rent(4096); + FMp4MessagePackWriter writer = new FMp4MessagePackWriter(buffer); + try + { + //ftyp + FileTypeBox fileTypeBox = new FileTypeBox(); + fileTypeBox.MajorBrand = "msdh"; + fileTypeBox.MinorVersion = "\0\0\0\0"; + fileTypeBox.CompatibleBrands.Add("isom"); + fileTypeBox.CompatibleBrands.Add("mp42"); + fileTypeBox.CompatibleBrands.Add("msdh"); + fileTypeBox.CompatibleBrands.Add("nsix"); + fileTypeBox.CompatibleBrands.Add("iso5"); + fileTypeBox.CompatibleBrands.Add("iso6"); + fileTypeBox.ToBuffer(ref writer); + var data = writer.FlushAndGetArray(); + return data; + } + finally + { + FMp4ArrayPool.Return(buffer); + } + } + + /// + /// 编码moov盒子 + /// + /// + public byte[] EncoderMoovBox(List nalus, int naluLength) + { + byte[] buffer = FMp4ArrayPool.Rent(naluLength + 4096); + FMp4MessagePackWriter writer = new FMp4MessagePackWriter(buffer); + try + { + var spsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == 7); + //SPS + spsNALU.RawData = h264Decoder.DiscardEmulationPreventionBytes(spsNALU.RawData); + var ppsNALU = nalus.FirstOrDefault(n => n.NALUHeader.NalUnitType == 8); + ppsNALU.RawData = h264Decoder.DiscardEmulationPreventionBytes(ppsNALU.RawData); + ExpGolombReader h264GolombReader = new ExpGolombReader(spsNALU.RawData); + var spsInfo = h264GolombReader.ReadSPS(); + //moov + MovieBox movieBox = new MovieBox(); + movieBox.MovieHeaderBox = new MovieHeaderBox(0, 2); + movieBox.MovieHeaderBox.CreationTime = 0; + movieBox.MovieHeaderBox.ModificationTime = 0; + movieBox.MovieHeaderBox.Duration = 0; + movieBox.MovieHeaderBox.Timescale = 1000; + movieBox.MovieHeaderBox.NextTrackID = 99; + movieBox.TrackBox = new TrackBox(); + movieBox.TrackBox.TrackHeaderBox = new TrackHeaderBox(0, 3); + movieBox.TrackBox.TrackHeaderBox.CreationTime = 0; + movieBox.TrackBox.TrackHeaderBox.ModificationTime = 0; + movieBox.TrackBox.TrackHeaderBox.TrackID = 1; + movieBox.TrackBox.TrackHeaderBox.Duration = 0; + movieBox.TrackBox.TrackHeaderBox.TrackIsAudio = false; + movieBox.TrackBox.TrackHeaderBox.Width = (uint)spsInfo.width; + movieBox.TrackBox.TrackHeaderBox.Height = (uint)spsInfo.height; + movieBox.TrackBox.MediaBox = new MediaBox(); + movieBox.TrackBox.MediaBox.MediaHeaderBox = new MediaHeaderBox(); + movieBox.TrackBox.MediaBox.MediaHeaderBox.CreationTime = 0; + movieBox.TrackBox.MediaBox.MediaHeaderBox.ModificationTime = 0; + movieBox.TrackBox.MediaBox.MediaHeaderBox.Timescale = 1200000; + movieBox.TrackBox.MediaBox.MediaHeaderBox.Duration = 0; + movieBox.TrackBox.MediaBox.HandlerBox = new HandlerBox(); + movieBox.TrackBox.MediaBox.HandlerBox.HandlerType = HandlerType.vide; + movieBox.TrackBox.MediaBox.HandlerBox.Name = "VideoHandler"; + movieBox.TrackBox.MediaBox.MediaInformationBox = new MediaInformationBox(); + movieBox.TrackBox.MediaBox.MediaInformationBox.VideoMediaHeaderBox = new VideoMediaHeaderBox(); + movieBox.TrackBox.MediaBox.MediaInformationBox.DataInformationBox = new DataInformationBox(); + movieBox.TrackBox.MediaBox.MediaInformationBox.DataInformationBox.DataReferenceBox = new DataReferenceBox(); + movieBox.TrackBox.MediaBox.MediaInformationBox.DataInformationBox.DataReferenceBox.DataEntryBoxes = new List(); + movieBox.TrackBox.MediaBox.MediaInformationBox.DataInformationBox.DataReferenceBox.DataEntryBoxes.Add(new DataEntryUrlBox(1)); + movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox = new SampleTableBox(); + movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SampleDescriptionBox = new SampleDescriptionBox(movieBox.TrackBox.MediaBox.HandlerBox.HandlerType); + movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SampleDescriptionBox.SampleEntries = new List(); + AVC1SampleEntry avc1 = new AVC1SampleEntry(); + avc1.AVCConfigurationBox = new AVCConfigurationBox(); + //h264 + avc1.Width = (ushort)movieBox.TrackBox.TrackHeaderBox.Width; + avc1.Height = (ushort)movieBox.TrackBox.TrackHeaderBox.Height; + avc1.AVCConfigurationBox.AVCLevelIndication = spsInfo.levelIdc; + avc1.AVCConfigurationBox.AVCProfileIndication = spsInfo.profileIdc; + avc1.AVCConfigurationBox.ProfileCompatibility = (byte)spsInfo.profileCompat; + avc1.AVCConfigurationBox.PPSs = new List() { ppsNALU.RawData }; + avc1.AVCConfigurationBox.SPSs = new List() { spsNALU.RawData }; + movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SampleDescriptionBox.SampleEntries.Add(avc1); + movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.TimeToSampleBox = new TimeToSampleBox() { + TimeToSampleInfos=new List + { + new TimeToSampleBox.TimeToSampleInfo + { + SampleCount=0, + SampleDelta=0 + } + } + }; + movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SampleToChunkBox = new SampleToChunkBox() { + SampleToChunkInfos=new List() + { + new SampleToChunkBox.SampleToChunkInfo + { + + } + } + }; + movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.SampleSizeBox = new SampleSizeBox() { + EntrySize = new List() + { + 0 + } + }; + movieBox.TrackBox.MediaBox.MediaInformationBox.SampleTableBox.ChunkOffsetBox = new ChunkOffsetBox() { + ChunkOffset=new List() + { + 0 + } + }; + movieBox.MovieExtendsBox = new MovieExtendsBox(); + movieBox.MovieExtendsBox.TrackExtendsBoxs = new List(); + TrackExtendsBox trex = new TrackExtendsBox(); + trex.TrackID = 1; + trex.DefaultSampleDescriptionIndex = 1; + trex.DefaultSampleDuration = 0; + trex.DefaultSampleSize = 0; + trex.DefaultSampleFlags = 0; + movieBox.MovieExtendsBox.TrackExtendsBoxs.Add(trex); + movieBox.ToBuffer(ref writer); + var data = writer.FlushAndGetArray(); + return data; + } + finally + { + FMp4ArrayPool.Return(buffer); + } + } + + + /// + /// 编码Moof盒子 + /// + /// + public byte[] EncoderMoofBox(List nalus, int naluLength,ulong timestamp, uint keyframeFlag,uint moofOffset=0) + { + byte[] buffer = FMp4ArrayPool.Rent(naluLength + 4096); + FMp4MessagePackWriter writer = new FMp4MessagePackWriter(buffer); + try + { + var movieFragmentBox = new MovieFragmentBox(); + movieFragmentBox.MovieFragmentHeaderBox = new MovieFragmentHeaderBox(); + movieFragmentBox.MovieFragmentHeaderBox.SequenceNumber = sn++; + movieFragmentBox.TrackFragmentBox = new TrackFragmentBox(); + //0x39 写文件 + //0x02 分段 + movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox = new TrackFragmentHeaderBox(2); + movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.TrackID = 1; + movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.DefaultSampleDuration = 48000; + movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.DefaultSampleSize = (uint)naluLength; + movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.DefaultSampleFlags = 0x1010000; + movieFragmentBox.TrackFragmentBox.TrackFragmentBaseMediaDecodeTimeBox = new TrackFragmentBaseMediaDecodeTimeBox(); + //trun + //0x39 写文件 + //0x02 分段 + uint flag = 0u; + if (!first) + { + flag = 4u; + movieFragmentBox.TrackFragmentBox.TrackFragmentBaseMediaDecodeTimeBox.BaseMediaDecodeTime = 0; + movieFragmentBox.TrackFragmentBox.TrackRunBox = new TrackRunBox(flags: flag); + first = true; + } + else + { + flag = 0x000400; + movieFragmentBox.TrackFragmentBox.TrackFragmentBaseMediaDecodeTimeBox.BaseMediaDecodeTime = timestamp * 1000; + movieFragmentBox.TrackFragmentBox.TrackRunBox = new TrackRunBox(flags: flag); + } + movieFragmentBox.TrackFragmentBox.TrackRunBox.FirstSampleFlags = 0; + movieFragmentBox.TrackFragmentBox.TrackRunBox.TrackRunInfos = new List(); + movieFragmentBox.TrackFragmentBox.TrackRunBox.TrackRunInfos.Add(new TrackRunBox.TrackRunInfo()); + + movieFragmentBox.TrackFragmentBox.TrackRunBox.TrackRunInfos.Add(new TrackRunBox.TrackRunInfo() + { + SampleSize = (uint)naluLength, + //SampleCompositionTimeOffset = package.Label3.DataType == JT1078DataType.视频I帧 ? package.LastIFrameInterval : package.LastFrameInterval, + SampleFlags = flag + }); + + movieFragmentBox.ToBuffer(ref writer); + var data = writer.FlushAndGetArray(); + return data; + } + finally + { + FMp4ArrayPool.Return(buffer); + } + } + + /// + /// 编码Mdat盒子 + /// + /// + public byte[] EncoderMdatBox(List nalus, int naluLength) + { + byte[] buffer = FMp4ArrayPool.Rent(naluLength + 4096); + FMp4MessagePackWriter writer = new FMp4MessagePackWriter(buffer); + try + { + var mediaDataBox = new MediaDataBox(); + mediaDataBox.Data = nalus.Select(s => s.RawData).ToList(); + mediaDataBox.ToBuffer(ref writer); + var data = writer.FlushAndGetArray(); + return data; + } + finally + { + FMp4ArrayPool.Return(buffer); + } + } + /// /// 编码首个视频盒子 /// @@ -43,7 +272,7 @@ namespace JT1078.FMp4 public byte[] EncoderFirstVideoBox(JT1078Package package) { byte[] buffer = FMp4ArrayPool.Rent(package.Bodies.Length + 4096); - FMp4MessagePackWriter writer = new FMp4MessagePackWriter(new byte[10 * 1024 * 1024]); + FMp4MessagePackWriter writer = new FMp4MessagePackWriter(buffer); try { var nalus = h264Decoder.ParseNALU(package); @@ -133,6 +362,10 @@ namespace JT1078.FMp4 } } + uint sn = 1; + + bool first = false; + /// /// 编码其他视频数据盒子 /// @@ -148,9 +381,11 @@ namespace JT1078.FMp4 var nalus = h264Decoder.ParseNALU(package); var movieFragmentBox = new MovieFragmentBox(); movieFragmentBox.MovieFragmentHeaderBox = new MovieFragmentHeaderBox(); - movieFragmentBox.MovieFragmentHeaderBox.SequenceNumber = package.SN; + movieFragmentBox.MovieFragmentHeaderBox.SequenceNumber = sn++; movieFragmentBox.TrackFragmentBox = new TrackFragmentBox(); - movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox = new TrackFragmentHeaderBox(0x39); + //0x39 写文件 + //0x02 分段 + movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox = new TrackFragmentHeaderBox(2); movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.TrackID = 1; movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.BaseDataOffset = moofOffset; movieFragmentBox.TrackFragmentBox.TrackFragmentHeaderBox.DefaultSampleDuration = 48000; @@ -159,10 +394,22 @@ namespace JT1078.FMp4 movieFragmentBox.TrackFragmentBox.TrackFragmentBaseMediaDecodeTimeBox = new TrackFragmentBaseMediaDecodeTimeBox(); movieFragmentBox.TrackFragmentBox.TrackFragmentBaseMediaDecodeTimeBox.BaseMediaDecodeTime = package.Timestamp * 1000; //trun - movieFragmentBox.TrackFragmentBox.TrackRunBox = new TrackRunBox(flags: 0x5); + //0x39 写文件 + //0x02 分段 + uint flag = package.Label3.DataType == JT1078DataType.视频I帧 ? 1u : 0u; + movieFragmentBox.TrackFragmentBox.TrackRunBox = new TrackRunBox(flags: 0x000400); movieFragmentBox.TrackFragmentBox.TrackRunBox.FirstSampleFlags = 0; movieFragmentBox.TrackFragmentBox.TrackRunBox.TrackRunInfos = new List(); - movieFragmentBox.TrackFragmentBox.TrackRunBox.TrackRunInfos.Add(new TrackRunBox.TrackRunInfo()); + //movieFragmentBox.TrackFragmentBox.TrackRunBox.TrackRunInfos.Add(new TrackRunBox.TrackRunInfo()); + foreach (var nalu in nalus) + { + movieFragmentBox.TrackFragmentBox.TrackRunBox.TrackRunInfos.Add(new TrackRunBox.TrackRunInfo() + { + SampleSize = (uint)nalu.RawData.Length, + SampleCompositionTimeOffset = package.Label3.DataType == JT1078DataType.视频I帧 ? package.LastIFrameInterval : package.LastFrameInterval, + SampleFlags = flag + }); + } movieFragmentBox.ToBuffer(ref writer); var mediaDataBox = new MediaDataBox(); mediaDataBox.Data = nalus.Select(s => s.RawData).ToList(); diff --git a/src/JT1078.FMp4/JT1078.FMp4.xml b/src/JT1078.FMp4/JT1078.FMp4.xml index bed1c8a..bad212c 100644 --- a/src/JT1078.FMp4/JT1078.FMp4.xml +++ b/src/JT1078.FMp4/JT1078.FMp4.xml @@ -1219,6 +1219,7 @@ moof n mdat n mfra + ref: https://www.w3.org/TR/mse-byte-stream-format-isobmff/#movie-fragment-relative-addressing @@ -1226,6 +1227,30 @@ + + + 编码ftyp盒子 + + + + + + 编码moov盒子 + + + + + + 编码Moof盒子 + + + + + + 编码Mdat盒子 + + + 编码首个视频盒子 diff --git a/src/JT1078.SignalR.Test/Hubs/FMp4Hub.cs b/src/JT1078.SignalR.Test/Hubs/FMp4Hub.cs new file mode 100644 index 0000000..cd4aee2 --- /dev/null +++ b/src/JT1078.SignalR.Test/Hubs/FMp4Hub.cs @@ -0,0 +1,48 @@ +using JT1078.SignalR.Test.Services; +using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Text.Json; +using System.Threading.Tasks; + + +namespace JT1078.SignalR.Test.Hubs +{ + + public class FMp4Hub : Hub + { + private readonly ILogger logger; + private readonly WsSession wsSession; + + public FMp4Hub( + WsSession wsSession, + ILoggerFactory loggerFactory) + { + this.wsSession = wsSession; + logger = loggerFactory.CreateLogger(); + } + + public override Task OnConnectedAsync() + { + if (logger.IsEnabled(LogLevel.Debug)) + { + logger.LogDebug($"链接上:{Context.ConnectionId}"); + } + wsSession.TryAdd(Context.ConnectionId); + return base.OnConnectedAsync(); + } + + public override Task OnDisconnectedAsync(Exception exception) + { + if (logger.IsEnabled(LogLevel.Debug)) + { + logger.LogDebug($"断开链接:{Context.ConnectionId}"); + } + wsSession.TryRemove(Context.ConnectionId); + return base.OnDisconnectedAsync(exception); + } + } +} diff --git a/src/JT1078.SignalR.Test/JT1078.SignalR.Test.csproj b/src/JT1078.SignalR.Test/JT1078.SignalR.Test.csproj new file mode 100644 index 0000000..ea59093 --- /dev/null +++ b/src/JT1078.SignalR.Test/JT1078.SignalR.Test.csproj @@ -0,0 +1,24 @@ + + + + net5.0 + + + + + + + + Always + + + Always + + + Always + + + Always + + + diff --git a/src/JT1078.SignalR.Test/Program.cs b/src/JT1078.SignalR.Test/Program.cs new file mode 100644 index 0000000..4045d84 --- /dev/null +++ b/src/JT1078.SignalR.Test/Program.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace JT1078.SignalR.Test +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} diff --git a/src/JT1078.SignalR.Test/Services/ToWebSocketService.cs b/src/JT1078.SignalR.Test/Services/ToWebSocketService.cs new file mode 100644 index 0000000..d1f8a06 --- /dev/null +++ b/src/JT1078.SignalR.Test/Services/ToWebSocketService.cs @@ -0,0 +1,108 @@ +using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System; +using System.Text; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Options; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using JT1078.SignalR.Test.Hubs; +using JT1078.FMp4; +using JT1078.Protocol; +using System.IO; +using JT1078.Protocol.Extensions; +using JT1078.Protocol.H264; + +namespace JT1078.SignalR.Test.Services +{ + public class ToWebSocketService: BackgroundService + { + private readonly ILogger logger; + + private readonly IHubContext _hubContext; + + private readonly FMp4Encoder fMp4Encoder; + + private readonly WsSession wsSession; + + private readonly H264Decoder h264Decoder; + + public ToWebSocketService( + H264Decoder h264Decoder, + WsSession wsSession, + FMp4Encoder fMp4Encoder, + ILoggerFactory loggerFactory, + IHubContext hubContext) + { + this.h264Decoder = h264Decoder; + logger = loggerFactory.CreateLogger(); + this.fMp4Encoder = fMp4Encoder; + _hubContext = hubContext; + this.wsSession = wsSession; + } + + public Queue q = new Queue(); + + public void a() + { + List packages = new List(); + var lines = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "H264", "jt1078_3.txt")); + int mergeBodyLength = 0; + foreach (var line in lines) + { + var data = line.Split(','); + var bytes = data[6].ToHexBytes(); + JT1078Package package = JT1078Serializer.Deserialize(bytes); + mergeBodyLength += package.DataBodyLength; + var packageMerge = JT1078Serializer.Merge(package); + if (packageMerge != null) + { + packages.Add(packageMerge); + } + } + var ftyp = fMp4Encoder.EncoderFtypBox(); + q.Enqueue(ftyp); + var package1 = packages[0]; + var nalus1 = h264Decoder.ParseNALU(package1); + var moov = fMp4Encoder.EncoderMoovBox(nalus1, package1.Bodies.Length); + q.Enqueue(moov); + var flag = package1.Label3.DataType == Protocol.Enums.JT1078DataType.视频I帧 ? 1u : 0u; + var moofBuffer = fMp4Encoder.EncoderMoofBox(nalus1, package1.Bodies.Length, package1.Timestamp, flag); + q.Enqueue(moofBuffer); + foreach (var package in packages) + { + var otherNalus = h264Decoder.ParseNALU(package); + var otherMdatBuffer = fMp4Encoder.EncoderMdatBox(otherNalus, package.Bodies.Length); + q.Enqueue(otherMdatBuffer); + } + } + + protected async override Task ExecuteAsync(CancellationToken stoppingToken) + { + a(); + while (!stoppingToken.IsCancellationRequested) + { + try + { + if (wsSession.GetCount() > 0) + { + if (q.Count > 0) + { + await _hubContext.Clients.All.SendAsync("video", q.Dequeue(), stoppingToken); + } + } + } + catch (Exception ex) + { + logger.LogError(ex,""); + } + await Task.Delay(1000); + } + } + } +} diff --git a/src/JT1078.SignalR.Test/Services/WsSession.cs b/src/JT1078.SignalR.Test/Services/WsSession.cs new file mode 100644 index 0000000..5c7b8e7 --- /dev/null +++ b/src/JT1078.SignalR.Test/Services/WsSession.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace JT1078.SignalR.Test.Services +{ + public class WsSession + { + private ConcurrentDictionary sessions; + + public WsSession() + { + sessions = new ConcurrentDictionary(); + } + + public void TryAdd(string connectionId) + { + sessions.TryAdd(connectionId, connectionId); + } + + public int GetCount() + { + return sessions.Count; + } + + public void TryRemove(string connectionId) + { + sessions.TryRemove(connectionId,out _); + } + } +} diff --git a/src/JT1078.SignalR.Test/Startup.cs b/src/JT1078.SignalR.Test/Startup.cs new file mode 100644 index 0000000..0e70236 --- /dev/null +++ b/src/JT1078.SignalR.Test/Startup.cs @@ -0,0 +1,66 @@ +using JT1078.FMp4; +using JT1078.Protocol.H264; +using JT1078.SignalR.Test.Hubs; +using JT1078.SignalR.Test.Services; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace JT1078.SignalR.Test +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddControllers(); + services.AddSignalR(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddHostedService(); + services.AddCors(options => options.AddPolicy("CorsPolicy", builder => + { + builder.AllowAnyMethod() + .AllowAnyHeader() + .AllowCredentials() + .SetIsOriginAllowed(o => true); + })); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + app.UseRouting(); + app.UseCors("CorsPolicy"); + app.UseAuthorization(); + app.UseEndpoints(endpoints => + { + endpoints.MapHub("/FMp4Hub"); + }); + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } + } +} diff --git a/src/JT1078.SignalR.Test/appsettings.Development.json b/src/JT1078.SignalR.Test/appsettings.Development.json new file mode 100644 index 0000000..8983e0f --- /dev/null +++ b/src/JT1078.SignalR.Test/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/src/JT1078.SignalR.Test/appsettings.json b/src/JT1078.SignalR.Test/appsettings.json new file mode 100644 index 0000000..d9d9a9b --- /dev/null +++ b/src/JT1078.SignalR.Test/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/src/JT1078.sln b/src/JT1078.sln index 5486098..1c9bde7 100644 --- a/src/JT1078.sln +++ b/src/JT1078.sln @@ -40,6 +40,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Benchmarks", "Benchmarks", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT1078.AV.Benchmark", "JT1078.AV.Benchmark\JT1078.AV.Benchmark.csproj", "{93D6C094-5A3A-4DFA-B52B-605FDFFB6094}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JT1078.SignalR.Test", "JT1078.SignalR.Test\JT1078.SignalR.Test.csproj", "{6A063AF3-611F-4A1C-ACCF-BF903B7C7014}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -218,6 +220,18 @@ Global {93D6C094-5A3A-4DFA-B52B-605FDFFB6094}.Release|x64.Build.0 = Release|Any CPU {93D6C094-5A3A-4DFA-B52B-605FDFFB6094}.Release|x86.ActiveCfg = Release|Any CPU {93D6C094-5A3A-4DFA-B52B-605FDFFB6094}.Release|x86.Build.0 = Release|Any CPU + {6A063AF3-611F-4A1C-ACCF-BF903B7C7014}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6A063AF3-611F-4A1C-ACCF-BF903B7C7014}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6A063AF3-611F-4A1C-ACCF-BF903B7C7014}.Debug|x64.ActiveCfg = Debug|Any CPU + {6A063AF3-611F-4A1C-ACCF-BF903B7C7014}.Debug|x64.Build.0 = Debug|Any CPU + {6A063AF3-611F-4A1C-ACCF-BF903B7C7014}.Debug|x86.ActiveCfg = Debug|Any CPU + {6A063AF3-611F-4A1C-ACCF-BF903B7C7014}.Debug|x86.Build.0 = Debug|Any CPU + {6A063AF3-611F-4A1C-ACCF-BF903B7C7014}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6A063AF3-611F-4A1C-ACCF-BF903B7C7014}.Release|Any CPU.Build.0 = Release|Any CPU + {6A063AF3-611F-4A1C-ACCF-BF903B7C7014}.Release|x64.ActiveCfg = Release|Any CPU + {6A063AF3-611F-4A1C-ACCF-BF903B7C7014}.Release|x64.Build.0 = Release|Any CPU + {6A063AF3-611F-4A1C-ACCF-BF903B7C7014}.Release|x86.ActiveCfg = Release|Any CPU + {6A063AF3-611F-4A1C-ACCF-BF903B7C7014}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -231,6 +245,7 @@ Global {5564C20B-BFF4-4A2A-BDF2-C7427E93E993} = {0655AF84-E578-409F-AB0E-B47E0D2F6814} {56E76D56-4CCC-401F-B25D-9AB41D58A10A} = {0655AF84-E578-409F-AB0E-B47E0D2F6814} {93D6C094-5A3A-4DFA-B52B-605FDFFB6094} = {807ADB1F-FED4-4A56-82D2-F08F1FB7C886} + {6A063AF3-611F-4A1C-ACCF-BF903B7C7014} = {0655AF84-E578-409F-AB0E-B47E0D2F6814} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {FAE1656D-226F-4B4B-8C33-615D7E632B26}