Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

741 lines
26 KiB

  1. //========= Copyright � Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Broadcasting HLTV demo in 3-second chunks, preparing for SteamCast
  4. // TODO test cases: start broadcast before relay server is ready; quit the server while broadcast hasn't flushed yet, make sure broadcast is properly stopped, flush is done and sends returned at least one confirm or error before quitting.
  5. //
  6. //=============================================================================//
  7. #include <tier1/strtools.h>
  8. #include <eiface.h>
  9. #include <bitbuf.h>
  10. #include <time.h>
  11. #include "hltvbroadcast.h"
  12. #include "hltvserver.h"
  13. #include "demo.h"
  14. #include "host_cmd.h"
  15. #include "demofile/demoformat.h"
  16. #include "filesystem_engine.h"
  17. #include "net.h"
  18. #include "networkstringtable.h"
  19. #include "dt_common_eng.h"
  20. #include "host.h"
  21. #include "server.h"
  22. #include "sv_steamauth.h"
  23. #ifndef DEDICATED
  24. #include "cl_steamauth.h"
  25. #endif
  26. #include "networkstringtableclient.h"
  27. #include "tier1/fmtstr.h"
  28. // memdbgon must be the last include file in a .cpp file!!!
  29. #include "tier0/memdbgon.h"
  30. extern CNetworkStringTableContainer *networkStringTableContainerServer;
  31. static ISteamHTTP *s_pSteamHTTP = NULL;
  32. ConVar tv_broadcast_keyframe_interval( "tv_broadcast_keyframe_interval", "3", FCVAR_RELEASE, "The frequency, in seconds, of sending keyframes and delta fragments to the broadcast relay server" );
  33. ConVar tv_broadcast_startup_resend_interval( "tv_broadcast_startup_resend_interval", "10", FCVAR_RELEASE, "The interval, in seconds, of re-sending startup data to the broadcast relay server (useful in case relay crashes, restarts or startup data http request fails)" );
  34. ConVar tv_broadcast_max_requests( "tv_broadcast_max_requests", "20", FCVAR_RELEASE, "Max number of broadcast http requests in flight. If there is a network issue, the requests may start piling up, degrading server performance. If more than the specified number of requests are in flight, the new requests are dropped." );
  35. ConVar tv_broadcast_drop_fragments( "tv_broadcast_drop_fragments", "0", FCVAR_RELEASE | FCVAR_HIDDEN, "Drop every Nth fragment" );
  36. ConVar tv_broadcast_terminate( "tv_broadcast_terminate", "1", FCVAR_RELEASE | FCVAR_HIDDEN, "Terminate every broadcast with a stop command" );
  37. ConVar tv_broadcast_origin_auth( "tv_broadcast_origin_auth", "gocastauth" /*use something secure, like hMugYm7Lv4o5*/, FCVAR_RELEASE | FCVAR_HIDDEN, "X-Origin-Auth header of the broadcast POSTs" );
  38. //////////////////////////////////////////////////////////////////////
  39. // Construction/Destruction
  40. //////////////////////////////////////////////////////////////////////
  41. CHLTVBroadcast::CHLTVBroadcast(CHLTVServer *pHltvServer) :m_pHltvServer( pHltvServer )
  42. {
  43. m_flTimeout = 30;
  44. m_pFile = NULL;
  45. m_bIsRecording = false;
  46. m_nHttpRequestBacklogHighWatermark = 0;
  47. m_nMatchFragmentCounter = 0;
  48. m_flBroadcastKeyframeInterval = tv_broadcast_keyframe_interval.GetFloat();
  49. }
  50. CHLTVBroadcast::CMemoryStream::CMemoryStream() : m_Buffer( NET_MAX_PAYLOAD, NET_MAX_PAYLOAD )
  51. {
  52. m_nCommitted = 0;
  53. m_pReserved = NULL;
  54. }
  55. CHLTVBroadcast::~CHLTVBroadcast()
  56. {
  57. StopRecording();
  58. }
  59. void CHLTVBroadcast::OnMasterStarted()
  60. {
  61. // we need some kind of unique id per match, when we can't have MatchID and use SteamID. This is it - it doesn't change if you restart broadcast during a match, but it changes every time a master proxy starts, which signifies a (re)start of tv server, which will always happen at the beginning of a match.
  62. m_nMasterCookie = Plat_GetTime();
  63. m_nMatchFragmentCounter = 0; // new match, start from the new match fragment counter
  64. }
  65. void CHLTVBroadcast::StartRecording( const char *pBroadcastUrl )
  66. {
  67. StopRecording(); // stop if we're already recording
  68. #ifndef DEDICATED
  69. s_pSteamHTTP = Steam3Client().SteamHTTP();
  70. #endif
  71. if ( !s_pSteamHTTP )
  72. s_pSteamHTTP = Steam3Server().SteamHTTP();
  73. ConMsg( "Recording Broadcast\n" );
  74. m_flBroadcastKeyframeInterval = tv_broadcast_keyframe_interval.GetFloat();
  75. m_nLastWrittenTick = 0;
  76. m_nFrameCount = 0;
  77. m_nKeyframeTick = 0; // when broadcasting starts, we immediately send a keyframe
  78. //Tag it as -1 so we can really set the start tick when the first frame is written
  79. m_nStartTick = -1;
  80. m_nCurrentTick = 0;
  81. m_nSignonDataAckTick = -1;
  82. m_nSignonDataFragment = -1;
  83. m_bIsRecording = true;
  84. m_nDeltaTick = -1;
  85. m_mpKeyframe.Reset();
  86. m_mpLowLevelSend.Reset();
  87. m_nMaxKeyframeTicks = 0;
  88. m_nMaxLowLevelSendTicks = 0;
  89. m_nDecayMaxKeyframeTicks = 0;
  90. m_nKeyframeBytes = m_nDeltaFrameBytes = 0;
  91. m_nFailedHttpRequests = 0;
  92. m_mpFrame.Reset();
  93. // extern ConVar sv_mmqueue_reservation;
  94. if ( uint64 nMatchId = sv.GetMatchId() )
  95. {
  96. int nHltvInstance = m_pHltvServer->GetInstanceIndex();
  97. m_Url.Format( "%s/%llui%d", pBroadcastUrl, nMatchId, nHltvInstance );
  98. }
  99. else
  100. {
  101. uint64 nSteamId = Steam3Server().GetGSSteamID().ConvertToUint64();
  102. m_Url.Format( "%s/s%llut%llu", pBroadcastUrl, nSteamId, m_nMasterCookie );
  103. }
  104. }
  105. bool CHLTVBroadcast::IsRecording()
  106. {
  107. return m_bIsRecording;
  108. }
  109. void CHLTVBroadcast::StopRecording( )
  110. {
  111. if ( !m_bIsRecording )
  112. return;
  113. DevMsg( "Stop Broadcast @frag %d\n", m_nMatchFragmentCounter );
  114. if ( tv_broadcast_terminate.GetBool() )
  115. m_DeltaStream.WriteCmdHeader( dem_stop, GetRecordingTick(), 0 );
  116. FlushCollectedStreams( "&final" );
  117. m_nMatchFragmentCounter += 2; // we need to create a hole in the broadcast in case we change our mind and start broadcast again some moments (or hours) later. This will force all clients to re-sync to the new keyframe
  118. m_DeltaStream.Purge();
  119. m_SignonDataStream.Reset();
  120. m_SignonDataStream.Purge();
  121. m_nStartTick = -1;
  122. m_nSignonDataAckTick = -1;
  123. for ( int i = 0; i < m_HttpRequests.Count(); ++i )
  124. m_HttpRequests[ i ]->DetachFromParent(); // TODO: if we're quitting, make sure that all the http requests finished before destroying the broadcast object
  125. m_HttpRequests.Purge(); // the elements will delete themselves
  126. // Note: some data fragments will still be in flight until they finish. We may start and stop another recording, and that's fine, SteamHTTP will continue uploading those data chunks until done or failed.
  127. m_bIsRecording = false;
  128. }
  129. void CHLTVBroadcast::WriteServerInfo( CMemoryStream &stream )
  130. {
  131. stream.WriteCmdHeader( dem_signon, GetRecordingTick(), 0 );
  132. CInStreamMsgWithSize msg( stream, "CHLTVBroadcast::WriteServerInfo" );
  133. // on the master demos are using sv object, on relays hltv
  134. CBaseServer *pServer = m_pHltvServer->IsMasterProxy()?(CBaseServer*)(&sv):(CBaseServer*)(m_pHltvServer);
  135. CSVCMsg_ServerInfo_t serverinfo;
  136. m_pHltvServer->FillServerInfo( serverinfo ); // fill rest of info message
  137. serverinfo.WriteToBuffer( msg );
  138. // send first tick
  139. CNETMsg_Tick_t signonTick( m_nSignonTick, 0, 0, 0 );
  140. signonTick.WriteToBuffer( msg );
  141. // Write replicated ConVars to non-listen server clients only
  142. CNETMsg_SetConVar_t convars;
  143. // build a list of all replicated convars
  144. Host_BuildConVarUpdateMessage( convars.mutable_convars(), FCVAR_REPLICATED, true );
  145. if ( m_pHltvServer->IsMasterProxy() )
  146. {
  147. // for SourceTV server demos write set "tv_transmitall 1" even
  148. // if it's off for the real broadcast
  149. convars.AddToTail( "tv_transmitall", "1" );
  150. m_pHltvServer->FixupConvars( convars );
  151. }
  152. // write convars to demo
  153. convars.WriteToBuffer( msg );
  154. // write stringtable baselines
  155. #ifndef SHARED_NET_STRING_TABLES
  156. if ( m_pHltvServer->m_StringTables )
  157. m_pHltvServer->m_StringTables->WriteBaselines( pServer->GetMapName(), msg );
  158. #endif
  159. // send signon state
  160. CNETMsg_SignonState_t signonMsg( SIGNONSTATE_NEW, pServer->GetSpawnCount() );
  161. signonMsg.WriteToBuffer( msg );
  162. }
  163. void CHLTVBroadcast::RecordCommand( const char *cmdstring )
  164. {
  165. if ( !IsRecording() )
  166. return;
  167. if ( !cmdstring || !cmdstring[0] )
  168. return;
  169. m_DeltaStream.WriteCmdHeader( dem_consolecmd, GetRecordingTick(), 0 );
  170. CInStreamMsg msg( m_DeltaStream, "RecordCommand", 1024 );
  171. CSVCMsg_Broadcast_Command_t cmd;
  172. cmd.set_cmd( cmdstring );
  173. }
  174. void CHLTVBroadcast::RecordServerClasses( CMemoryStream &stream, ServerClass *pClasses )
  175. {
  176. stream.WriteCmdHeader( dem_datatables, GetRecordingTick(), 0 );
  177. CInStreamMsg buf( stream, "CHLTVBroadcast::RecordServerClasses", DEMO_RECORD_BUFFER_SIZE );
  178. // Send SendTable info.
  179. DataTable_WriteSendTablesBuffer( pClasses, &buf );
  180. // Send class descriptions.
  181. DataTable_WriteClassInfosBuffer( pClasses, &buf );
  182. if ( buf.GetNumBitsLeft() <= 0 )
  183. {
  184. Sys_Error( "unable to record server classes\n" );
  185. }
  186. // this was a dem_datatables in the demo file
  187. }
  188. void CHLTVBroadcast::RecordStringTables( CMemoryStream &stream )
  189. {
  190. stream.WriteCmdHeader( dem_stringtables, GetRecordingTick(), 0 );
  191. CInStreamMsg buf( stream, "CHLTVBroadcast::RecordStringTables", DEMO_RECORD_BUFFER_SIZE );
  192. networkStringTableContainerServer->WriteStringTables( buf );
  193. }
  194. void CHLTVBroadcast::WriteSignonData()
  195. {
  196. // on the master demos are using sv object, on relays hltv
  197. CBaseServer *pServer = m_pHltvServer->IsMasterProxy()?(CBaseServer*)(&sv):(CBaseServer*)(m_pHltvServer);
  198. m_nSignonTick = pServer->m_nTickCount;
  199. m_SignonDataStream.Purge();
  200. m_nSignonDataFragment = m_nMatchFragmentCounter;
  201. WriteServerInfo( m_SignonDataStream );
  202. RecordServerClasses( m_SignonDataStream, serverGameDLL->GetAllServerClasses() );
  203. RecordStringTables( m_SignonDataStream );
  204. {
  205. m_SignonDataStream.WriteCmdHeader( dem_signon, GetRecordingTick(), 0 );
  206. CInStreamMsgWithSize msg( m_SignonDataStream, "CHLTVBroadcast::WriteSignonData" );
  207. // use your class infos, CRC is correct
  208. // use your class infos, CRC is correct
  209. CSVCMsg_ClassInfo_t classmsg;
  210. classmsg.set_create_on_client( true );
  211. classmsg.WriteToBuffer( msg );
  212. // Write the regular signon now
  213. msg.WriteBits( m_pHltvServer->m_Signon.GetData(), m_pHltvServer->m_Signon.GetNumBitsWritten() );
  214. // write new state
  215. CNETMsg_SignonState_t signonMsg1( SIGNONSTATE_PRESPAWN, pServer->GetSpawnCount() );
  216. signonMsg1.WriteToBuffer( msg );
  217. }
  218. {
  219. // Dump all accumulated avatar data messages into signon portion of the demo file
  220. FOR_EACH_MAP_FAST( m_pHltvServer->m_mapPlayerAvatarData, iData )
  221. {
  222. m_SignonDataStream.WriteCmdHeader( dem_signon, GetRecordingTick(), 0 );
  223. CInStreamMsgWithSize msg( m_SignonDataStream, "CHLTVBroadcast::WriteSignonData(avatar data)" );
  224. CNETMsg_PlayerAvatarData_t &msgPlayerAvatarData = *m_pHltvServer->m_mapPlayerAvatarData.Element( iData );
  225. msgPlayerAvatarData.WriteToBuffer( msg );
  226. }
  227. // For official tournament servers also dump all the avatars of possible players into signon portion
  228. extern ConVar sv_mmqueue_reservation;
  229. extern ConVar sv_reliableavatardata;
  230. if ( ( sv_reliableavatardata.GetInt() == 2 ) && ( sv_mmqueue_reservation.GetString()[ 0 ] == 'Q' ) )
  231. {
  232. // CSteamID steamIdGs( SteamGameServer() ? SteamGameServer()->GetSteamID() : CSteamID() );
  233. CSteamID steamIdGs( Steam3Server().GetGSSteamID() );
  234. if ( !steamIdGs.IsValid() ) steamIdGs.SetEUniverse( k_EUniversePublic );
  235. CSteamID steamIdUser( 1, steamIdGs.GetEUniverse(), k_EAccountTypeIndividual );
  236. CUtlVector< AccountID_t > arrAvatarsToAddFromDisk;
  237. for ( char const *pszPrev = sv_mmqueue_reservation.GetString(), *pszNext = pszPrev;
  238. ( pszNext = strchr( pszPrev, '[' ) ) != NULL; ( pszPrev = pszNext + 1 ) )
  239. {
  240. uint32 uiAccountId = 0;
  241. sscanf( pszNext, "[%x]", &uiAccountId );
  242. if ( uiAccountId && // valid account and not yet sent in previous loop
  243. ( m_pHltvServer->m_mapPlayerAvatarData.Find( uiAccountId ) == m_pHltvServer->m_mapPlayerAvatarData.InvalidIndex() ) &&
  244. ( arrAvatarsToAddFromDisk.Find( uiAccountId ) == arrAvatarsToAddFromDisk.InvalidIndex() ) )
  245. {
  246. arrAvatarsToAddFromDisk.AddToTail( uiAccountId );
  247. }
  248. }
  249. for ( char const *pszPrev = sv_mmqueue_reservation.GetString(), *pszNext = pszPrev;
  250. ( pszNext = strchr( pszPrev, '{' ) ) != NULL; ( pszPrev = pszNext + 1 ) )
  251. {
  252. uint32 uiAccountId = 0;
  253. sscanf( pszNext, "{%x}", &uiAccountId );
  254. if ( uiAccountId && // valid account and not yet sent in previous loop
  255. ( m_pHltvServer->m_mapPlayerAvatarData.Find( uiAccountId ) == m_pHltvServer->m_mapPlayerAvatarData.InvalidIndex() ) &&
  256. ( arrAvatarsToAddFromDisk.Find( uiAccountId ) == arrAvatarsToAddFromDisk.InvalidIndex() ) )
  257. {
  258. arrAvatarsToAddFromDisk.AddToTail( uiAccountId );
  259. }
  260. }
  261. FOR_EACH_VEC( arrAvatarsToAddFromDisk, i )
  262. {
  263. steamIdUser.SetAccountID( arrAvatarsToAddFromDisk[i] );
  264. //
  265. // Try to load the avatar data for this player
  266. //
  267. CUtlBuffer bufAvatarData;
  268. CUtlBuffer bufAvatarDataDefault;
  269. CUtlBuffer *pbufUseRgb = NULL;
  270. if ( !pbufUseRgb &&
  271. g_pFullFileSystem->ReadFile( CFmtStr( "avatars/%llu.rgb", steamIdUser.ConvertToUint64() ), "MOD", bufAvatarData ) &&
  272. ( bufAvatarData.TellPut() == 64 * 64 * 3 ) )
  273. pbufUseRgb = &bufAvatarData;
  274. if ( !pbufUseRgb &&
  275. g_pFullFileSystem->ReadFile( "avatars/default.rgb", "MOD", bufAvatarDataDefault ) &&
  276. ( bufAvatarDataDefault.TellPut() == 64 * 64 * 3 ) )
  277. pbufUseRgb = &bufAvatarDataDefault;
  278. if ( pbufUseRgb )
  279. {
  280. CNETMsg_PlayerAvatarData_t msgPlayerAvatarData;
  281. msgPlayerAvatarData.set_rgb( pbufUseRgb->Base(), pbufUseRgb->TellPut() );
  282. msgPlayerAvatarData.set_accountid( steamIdUser.GetAccountID() );
  283. m_SignonDataStream.WriteCmdHeader( dem_signon, GetRecordingTick(), 0 );
  284. CInStreamMsgWithSize msg( m_SignonDataStream, "CHLTVBroadcast::WriteSignonData(avatar data)" );
  285. msgPlayerAvatarData.WriteToBuffer( msg );
  286. }
  287. }
  288. }
  289. }
  290. {
  291. m_SignonDataStream.WriteCmdHeader( dem_signon, GetRecordingTick(), 0 );
  292. CInStreamMsgWithSize msg( m_SignonDataStream, "CHLTVBroadcast::WriteSignonData(tail)" );
  293. // set view entity
  294. CSVCMsg_SetView_t viewent;
  295. viewent.set_entity_index( m_pHltvServer->m_nViewEntity );
  296. viewent.WriteToBuffer( msg );
  297. // Spawned into server, not fully active, though
  298. CNETMsg_SignonState_t signonMsg2( SIGNONSTATE_SPAWN, pServer->GetSpawnCount() );
  299. signonMsg2.WriteToBuffer( msg );
  300. }
  301. }
  302. void CHLTVBroadcast::SendSignonData()
  303. {
  304. Assert( !m_SignonDataStream.IsEmpty() );
  305. Send( CFmtStr( "/%d/start?tick=%d&tps=%.1f&map=%s&protocol=%d", m_nSignonDataFragment, m_nStartTick, 1.0f / sv.GetTickInterval(), sv.GetMapName(), DEMO_PROTOCOL ), m_SignonDataStream );
  306. m_nSignonDataAckTick = m_nCurrentTick;
  307. }
  308. void CHLTVBroadcast::OnHttpRequestFailed()
  309. {
  310. m_nFailedHttpRequests++;
  311. }
  312. void CHLTVBroadcast::OnHttpRequestResetContent()
  313. {
  314. // may need to re-send the startup data - the http response says that the server hasn't received startup data yet at the time it was responding to the request
  315. if ( ( m_nCurrentTick - m_nSignonDataAckTick ) * sv.GetTickInterval() > tv_broadcast_startup_resend_interval.GetFloat() )
  316. {
  317. Msg( "Broadcast[%d] Re-sending signon data\n", m_pHltvServer->GetInstanceIndex() );
  318. // we haven't seen an OK response (meaning the startup tick is recognized by the server) in . Try to re-send
  319. SendSignonData();
  320. }
  321. }
  322. void CHLTVBroadcast::OnHttpRequestSuccess()
  323. {
  324. m_nSignonDataAckTick = m_nCurrentTick;
  325. }
  326. void CHLTVBroadcast::Register( CHttpCallback *pCallback )
  327. {
  328. m_HttpRequests.AddToTail( pCallback );
  329. m_nHttpRequestBacklogHighWatermark = Max( m_HttpRequests.Count(), m_nHttpRequestBacklogHighWatermark );
  330. }
  331. void CHLTVBroadcast::Unregister( CHttpCallback *pCallback )
  332. {
  333. m_HttpRequests.FindAndFastRemove( pCallback );
  334. }
  335. CON_COMMAND( tv_broadcast_resend, "resend broadcast data to broadcast relay" )
  336. {
  337. for ( CActiveHltvServerIterator hltv; hltv; hltv.Next() )
  338. hltv->m_Broadcast.ResendStartup();
  339. }
  340. void CHLTVBroadcast::ResendStartup()
  341. {
  342. if ( m_nStartTick != -1 )
  343. {
  344. // the signon data has been sent
  345. SendSignonData();
  346. }
  347. }
  348. void CHLTVBroadcast::WriteFrame( CHLTVFrame *pFrame, bf_write *additionaldata )
  349. {
  350. Assert( m_pHltvServer->IsMasterProxy() ); // this works only on the master since we use sv.
  351. m_nCurrentTick = pFrame->tick_count;
  352. bool bKeyFrame = ( m_nCurrentTick - m_nKeyframeTick ) * sv.GetTickInterval() >= m_flBroadcastKeyframeInterval;
  353. if ( m_nStartTick == -1 )
  354. {
  355. m_nStartTick = pFrame->tick_count;
  356. WriteSignonData();
  357. SendSignonData();
  358. bKeyFrame = true;
  359. }
  360. else
  361. {
  362. m_DeltaStream.WriteCmdHeader( dem_packet, GetRecordingTick(), 0 );
  363. CInStreamMsgWithSize msg( m_DeltaStream, "CHLTVBroadcast:Frame(delta)", NET_MAX_PAYLOAD );
  364. MICRO_PROFILE( m_mpFrame );
  365. RecordSnapshot( pFrame, additionaldata, msg, m_nDeltaTick );
  366. // update delta tick just like fakeclients do
  367. m_nDeltaTick = pFrame->tick_count;
  368. m_nDeltaFrameBytes += msg.GetNumBytesWritten();
  369. }
  370. m_nLastWrittenTick = pFrame->tick_count;
  371. m_nFrameCount++;
  372. if ( bKeyFrame )
  373. {
  374. if ( m_HttpRequests.Count() > tv_broadcast_max_requests.GetInt() )
  375. {
  376. int nFragment = ++m_nMatchFragmentCounter;
  377. Warning( "Broadcast backlog of http requests in flight is too high (%d > %d), dropping %d/full and %d/delta.\n",
  378. m_HttpRequests.Count(), tv_broadcast_max_requests.GetInt(), m_nMatchFragmentCounter, nFragment );
  379. }
  380. else
  381. {
  382. // upload the delta frames (IMPORTANT: including the current frame that we'll re-record the keyframe for) and the TOC (the last entry of TOC becomes useful only after we upload the current delta frames)
  383. // So, each 3-second fragment will full and delta frames has the full frame of the tick preceding the first delta frame tick
  384. FlushCollectedStreams();
  385. // it's been 3 seconds already, let's re-send the keyframe - starting a new fragment with a full frame update
  386. m_nKeyframeTick = m_nCurrentTick;
  387. int nFragment = ++m_nMatchFragmentCounter;
  388. Assert( m_DeltaStream.IsEmpty() );
  389. {
  390. m_DeltaStream.WriteCmdHeader( dem_packet, GetRecordingTick(), 0 );
  391. CInStreamMsgWithSize msg( m_DeltaStream, "CHLTVBroadcast:FullFrame", NET_MAX_PAYLOAD );
  392. CMicroProfilerSample sample;
  393. RecordSnapshot( pFrame, additionaldata, msg, -1 );
  394. int64 nElapsedTicks = sample.GetElapsed();
  395. m_mpKeyframe.Add( nElapsedTicks );
  396. m_nMaxKeyframeTicks = Max( m_nMaxKeyframeTicks, nElapsedTicks );
  397. m_nDecayMaxKeyframeTicks = Max( m_nDecayMaxKeyframeTicks * 933 / 1000, nElapsedTicks ); // 0.933 ^ 20 = .25 , this will decay 1/4 every minute
  398. }
  399. m_nKeyframeBytes += m_DeltaStream.GetCommitSize();
  400. Send( CFmtStr( "/%d/full?tick=%d", nFragment, m_nCurrentTick ), m_DeltaStream );
  401. }
  402. m_DeltaStream.Reset();
  403. }
  404. }
  405. void CHLTVBroadcast::DumpStats()
  406. {
  407. uint32 numKeyframes = m_nMatchFragmentCounter;
  408. int64 nKeyframeBytes = m_nKeyframeBytes, nDeltaFrameBytes = m_nDeltaFrameBytes;
  409. if ( numKeyframes > 1 )
  410. {
  411. // I'm missing the last keyframe, its delta portion is truncated
  412. nKeyframeBytes /= numKeyframes ;
  413. nDeltaFrameBytes /= numKeyframes ;
  414. }
  415. if ( numKeyframes )
  416. {
  417. Msg( "signup frag %d (%u bytes), frag counter %d.\nDelta frames: %s Bps, %.3f ms/frame.\nAvg Keyframe: %s Bytes, %.3f ms (%.1f max, %.1f recent max)\nHttp: %d failed, %d/%d max in flight\n",
  418. m_nSignonDataFragment, m_SignonDataStream.GetCommitSize(),
  419. m_nMatchFragmentCounter,
  420. V_pretifynum( int( nDeltaFrameBytes / m_flBroadcastKeyframeInterval ) ),
  421. m_mpFrame.GetAverageMilliseconds(),
  422. V_pretifynum( nKeyframeBytes ),
  423. m_mpKeyframe.GetAverageMilliseconds(),
  424. CMicroProfiler::TimeBaseTicksToMilliseconds( m_nMaxKeyframeTicks ),
  425. CMicroProfiler::TimeBaseTicksToMilliseconds( m_nDecayMaxKeyframeTicks ),
  426. m_nFailedHttpRequests, m_HttpRequests.Count(), m_nHttpRequestBacklogHighWatermark
  427. );
  428. Msg( "http Send %.3f ms ave, %.3f ms max\n", m_mpLowLevelSend.GetAverageMilliseconds(), CMicroProfiler::TimeBaseTicksToMilliseconds( m_nMaxLowLevelSendTicks ) );
  429. }
  430. else
  431. {
  432. Msg( "no keyframes written\n" );
  433. }
  434. }
  435. void CHLTVBroadcast::RecordSnapshot( CHLTVFrame * pFrame, bf_write * additionaldata, bf_write &msg, int nDeltaTick )
  436. {
  437. //first write reliable data
  438. bf_write *data = &pFrame->m_Messages[ HLTV_BUFFER_RELIABLE ];
  439. if ( data->GetNumBitsWritten() )
  440. msg.WriteBits( data->GetBasePointer(), data->GetNumBitsWritten() );
  441. //now send snapshot data
  442. if ( host_frameendtime_computationduration > .1f )
  443. DevMsg( "CHLTVBroadcast::RecordSnapshot tick %.1f ms\n", host_frameendtime_computationduration * 1000 );
  444. // send tick time
  445. CNETMsg_Tick_t tickmsg( pFrame->tick_count, host_frameendtime_computationduration, host_frametime_stddeviation, host_framestarttime_stddeviation );
  446. tickmsg.WriteToBuffer( msg );
  447. #ifndef SHARED_NET_STRING_TABLES
  448. // Update shared client/server string tables. Must be done before sending entities
  449. m_pHltvServer->m_StringTables->WriteUpdateMessage( NULL, MAX( m_nSignonTick, nDeltaTick ), msg );
  450. #endif
  451. // get delta frame
  452. CClientFrame *deltaFrame = m_pHltvServer->GetClientFrame( nDeltaTick ); // NULL if delta_tick is not found or -1
  453. // send entity update, delta compressed if deltaFrame != NULL
  454. CSVCMsg_PacketEntities_t packetmsg;
  455. sv.WriteDeltaEntities( m_pHltvServer->m_MasterClient, pFrame, deltaFrame, packetmsg );
  456. packetmsg.WriteToBuffer( msg );
  457. // send all unreliable temp ents between last and current frame
  458. CSVCMsg_TempEntities_t tempentsmsg;
  459. CFrameSnapshot * fromSnapshot = deltaFrame ? deltaFrame->GetSnapshot() : NULL;
  460. sv.WriteTempEntities( m_pHltvServer->m_MasterClient, pFrame->GetSnapshot(), fromSnapshot, tempentsmsg, 255 );
  461. if ( tempentsmsg.num_entries() )
  462. {
  463. tempentsmsg.WriteToBuffer( msg );
  464. }
  465. // write sound data
  466. data = &pFrame->m_Messages[ HLTV_BUFFER_SOUNDS ];
  467. if ( data->GetNumBitsWritten() )
  468. msg.WriteBits( data->GetBasePointer(), data->GetNumBitsWritten() );
  469. // write voice data
  470. data = &pFrame->m_Messages[ HLTV_BUFFER_VOICE ];
  471. if ( data->GetNumBitsWritten() )
  472. msg.WriteBits( data->GetBasePointer(), data->GetNumBitsWritten() );
  473. // last write unreliable data
  474. data = &pFrame->m_Messages[ HLTV_BUFFER_UNRELIABLE ];
  475. if ( data->GetNumBitsWritten() )
  476. msg.WriteBits( data->GetBasePointer(), data->GetNumBitsWritten() );
  477. if ( additionaldata && additionaldata->GetNumBitsWritten() )
  478. {
  479. msg.WriteBits( additionaldata->GetBasePointer(), additionaldata->GetNumBitsWritten() );
  480. }
  481. }
  482. void * CHLTVBroadcast::CMemoryStream::Reserve( uint nReserveBytes )
  483. {
  484. Assert( !m_pReserved );
  485. m_Buffer.EnsureCapacity( m_nCommitted + nReserveBytes );
  486. m_pReserved = m_Buffer.Base() + m_nCommitted;
  487. return m_pReserved;
  488. }
  489. void CHLTVBroadcast::CMemoryStream::Commit( uint nCommitBytes )
  490. {
  491. Assert( m_pReserved && m_nCommitted + nCommitBytes <= uint( m_Buffer.NumAllocated() ) );
  492. m_nCommitted += nCommitBytes;
  493. m_pReserved = NULL;
  494. }
  495. void CHLTVBroadcast::CMemoryStream::Purge()
  496. {
  497. Assert( !m_nCommitted && !m_pReserved ); // we should not have anything waiting to be committed
  498. m_Buffer.Purge();
  499. m_nCommitted = 0;
  500. m_pReserved = NULL;
  501. }
  502. void CHLTVBroadcast::CMemoryStream::WriteCmdHeader( unsigned char cmd, int tick, int nPlayerSlot )
  503. {
  504. unsigned char *pCmdHeader = ( unsigned char* ) Reserve( 6 );
  505. pCmdHeader[ 0 ] = cmd;
  506. *( int* )( pCmdHeader + 1 ) = tick;
  507. pCmdHeader[ 5 ] = nPlayerSlot;
  508. Commit( 6 );
  509. }
  510. void CHLTVBroadcast::FlushCollectedStreams( const char *pExtraParams )
  511. {
  512. // send the collected delta stream payload
  513. if ( !m_DeltaStream.IsEmpty() )
  514. {
  515. //int nBaseTick = m_Keyframes.IsEmpty() ? -1 : m_Keyframes.Tail().nKeyframeTick;
  516. Send( CFmtStr( "/%d/delta?endtick=%d%s", m_nMatchFragmentCounter, m_nCurrentTick, pExtraParams ), m_DeltaStream );
  517. m_DeltaStream.Reset();
  518. }
  519. }
  520. // protocol is very simple: a=<account/match id> & t= <type, i=initial/startup, k=keyframe/full frame, d=delta frames> &
  521. CHLTVBroadcast::CHttpCallback * CHLTVBroadcast::Send( const char* pPath, CMemoryStream &stream )
  522. {
  523. return Send( pPath, stream.Base(), stream.GetCommitSize() );
  524. }
  525. CHLTVBroadcast::CHttpCallback * CHLTVBroadcast::Send( const char* pPath, const void *pBase, uint nSize )
  526. {
  527. if ( !s_pSteamHTTP )
  528. {
  529. Warning( "HLTV Broadcast cannot send data because steam http is not available. Are you logged into Steam?\n" );
  530. return NULL;
  531. }
  532. if ( tv_broadcast_drop_fragments.GetInt() > 0 && ( rand() % tv_broadcast_drop_fragments.GetInt() ) == 0 )
  533. {
  534. Msg( "Dropping %d bytes to %s%s\n", nSize, GetUrl(), pPath );
  535. return NULL;
  536. }
  537. return LowLevelSend( m_Url + pPath, pBase, nSize );
  538. }
  539. CHLTVBroadcast::CHttpCallback * CHLTVBroadcast::LowLevelSend( const CUtlString &path, const void *pBase, uint nSize )
  540. {
  541. CMicroProfilerGuard mpg( &m_mpLowLevelSend );
  542. HTTPRequestHandle hRequest = s_pSteamHTTP->CreateHTTPRequest( k_EHTTPMethodPOST, path.Get() );
  543. if ( !hRequest )
  544. {
  545. Warning( "Cannot create http put: %s, %u bytes lost\n", path.Get(), nSize );
  546. return NULL;
  547. }
  548. s_pSteamHTTP->SetHTTPRequestNetworkActivityTimeout( hRequest, m_flTimeout );
  549. const char *pOriginAuth = tv_broadcast_origin_auth.GetString();
  550. if ( pOriginAuth && *pOriginAuth )
  551. {
  552. if ( !s_pSteamHTTP->SetHTTPRequestHeaderValue( hRequest, "X-Origin-Auth", tv_broadcast_origin_auth.GetString() ) )
  553. {
  554. Warning( "Cannot set http X-Origin-Auth\n" );
  555. }
  556. }
  557. if ( !s_pSteamHTTP->SetHTTPRequestRawPostBody( hRequest, "application/octet-stream", ( uint8* )pBase, nSize ) )
  558. {
  559. Warning( "Cannot set http post body for %s, %u bytes\n", path.Get(), nSize );
  560. s_pSteamHTTP->ReleaseHTTPRequest( hRequest );
  561. return NULL;
  562. }
  563. SteamAPICall_t hCall;
  564. CHttpCallback *pResult = NULL;
  565. if ( s_pSteamHTTP->SendHTTPRequest( hRequest, &hCall ) && hCall )
  566. {
  567. pResult = new CHttpCallback( this, hRequest, path.Get() );
  568. SteamAPI_RegisterCallResult( pResult, hCall );
  569. }
  570. else
  571. {
  572. s_pSteamHTTP->ReleaseHTTPRequest( hRequest );
  573. }
  574. m_nMaxLowLevelSendTicks = Max( m_nMaxLowLevelSendTicks, mpg.GetElapsed() );
  575. return pResult;
  576. }
  577. CHLTVBroadcast::CHttpCallback::CHttpCallback( CHLTVBroadcast *pParent, HTTPRequestHandle hRequest, const char *pResource ) :
  578. m_pParent( pParent ), m_hRequest( hRequest ), m_Resource( pResource )
  579. {
  580. m_iCallback = HTTPRequestCompleted_t::k_iCallback;
  581. pParent->Register( this );
  582. }
  583. CHLTVBroadcast::CHttpCallback::~CHttpCallback()
  584. {
  585. s_pSteamHTTP->ReleaseHTTPRequest( m_hRequest );
  586. if ( m_pParent )
  587. m_pParent->Unregister( this );
  588. }
  589. void CHLTVBroadcast::CHttpCallback::Run( void *pvParam )
  590. { // success!
  591. Run( pvParam, false, 0 );
  592. }
  593. void CHLTVBroadcast::CHttpCallback::Run( void *pvParam, bool bIOFailure, SteamAPICall_t hSteamAPICall )
  594. {
  595. EHTTPStatusCode nStatus = ( ( HTTPRequestCompleted_t * )pvParam )->m_eStatusCode;
  596. if ( bIOFailure )
  597. {
  598. Msg( "Broadcast[%d] IO Failure, Http code %d on %s\n", m_pParent ? m_pParent->m_pHltvServer->GetInstanceIndex() : -1, int( nStatus ), m_Resource.Get() );
  599. if ( m_pParent )
  600. m_pParent->OnHttpRequestFailed();
  601. }
  602. else
  603. {
  604. if ( nStatus == k_EHTTPStatusCode205ResetContent )
  605. {
  606. if ( m_pParent )
  607. m_pParent->OnHttpRequestResetContent();
  608. }
  609. else
  610. {
  611. if ( nStatus != k_EHTTPStatusCode200OK ) // we should always get a 200
  612. {
  613. Msg( "Broadcast[%d] Relay returned Http code %d on %s\n", m_pParent ? m_pParent->m_pHltvServer->GetInstanceIndex() : -1, int( nStatus ), m_Resource.Get() );
  614. }
  615. }
  616. }
  617. delete this;
  618. }