// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "CoreMinimal.h" #include "GameplayMediaEncoder.h" #include "Interfaces/IHttpRequest.h" #include "GameplayMediaEncoderCommon.h" #if defined(WITH_IBMRTMPINGEST) && LIVESTREAMING // IBM RTMP Ingest THIRD_PARTY_INCLUDES_START extern "C" { #include "rtmp_c/core/init.h" #include "rtmp_c/net/rtmp/rtmpclient.h" #include "rtmp_c/net/rtmp/modules/rtmpmodule_connect.h" #include "rtmp_c/net/rtmp/modules/rtmpmodule_broadcaster.h" #include "rtmp_c/core/rawdata.h" #include "rtmp_c/core/platform/threadfunc.h" #include "rtmp_c/codec/codecs.h" } THIRD_PARTY_INCLUDES_END class FJsonObject; DECLARE_LOG_CATEGORY_EXTERN(IbmLiveStreaming, Log, All); class FIbmLiveStreaming final : private IGameplayMediaEncoderListener { public: using FBandwidthProbeCallback = TFunction; FIbmLiveStreaming(); ~FIbmLiveStreaming(); static FIbmLiveStreaming* Get(); /** * Starts streaming, using best guess settings */ bool Start(); /** * Starts streaming, using explicit settings */ bool Start(const FString& ClientId, const FString& ClientSecret, const FString& Channel, uint32 AudioSampleRate, uint32 AudioNumChannels, uint32 AudioBitrate); /** * Stops streaming */ void Stop(); static void StartCmd() { // We call Get(), so it creates the singleton Get()->Start(); } static void StopCmd() { if (Singleton) { Singleton->Stop(); } } private: enum class EState { None, GettingAccessToken, // See https://developers.video.ibm.com/channel-api/getting-started.html#token-endpoint_8 GettingIngestSettings, // See https://developers.video.ibm.com/channel-api/channel.html#ingest-settings_77 Connecting, Connected, Stopping }; struct FRtmpUrl { FString Host; FString Application; FString Channel; FString ApplicationName; FString StreamName; FString StreamNamePrefix; }; struct FIngest { FString StreamingKey; FRtmpUrl RtmpUrl; }; struct FFormData { public: FFormData(); void AddField(const FString& Name, const FString& Value); // Call this in the end, to generate the HttpRequest template TSharedRef CreateHttpPostRequest(const FString& URL, T1&& TargetObj, T2&& TargetObjHandler) { auto HttpRequest = CreateHttpPostRequestImpl(URL); HttpRequest->OnProcessRequestComplete().BindRaw(std::forward(TargetObj), std::forward(TargetObjHandler)); return HttpRequest; } private: static TArray FStringToUint8(const FString& InString); FString BoundaryLabel; FString BoundaryBegin; FString BoundaryEnd; FString Data; TSharedRef CreateHttpPostRequestImpl(const FString& URL); }; // IGameplayMediaEncoderListener interface void OnMediaSample(const FGameplayMediaEncoderSample& Sample) override; /** * Callback from HTTP library when a request has completed * @param HttpRequest The request object * @param HttpResponse The response from the server * @param bSucceeded Whether a response was successfully received */ void OnProcessRequestComplete(FHttpRequestPtr SourceHttpRequest, FHttpResponsePtr HttpResponse, bool bSucceeded); static bool GetJsonField(const TSharedPtr& JsonObject, const FString& FieldName, FString& DestStr); static bool GetJsonField(const TSharedPtr& JsonObject, const FString& FieldName, const TSharedPtr*& DestObj); void Connect(); // Called to finalize a previous call to `Stop()`. This is called once // the IBM library tells us the stream stopped and was deleted void FinishStopOnGameThread(); void StopFromSocketThread(); // // Callbacks for IBM RTMP C Ingest // static void OnConnectionError(RTMPModuleConnect* Module, RTMPEvent Evt, void* RejectInfoObj); static void OnConnectionSuccess(RTMPModuleConnect* Module); static void OnStreamPublished(RTMPModuleBroadcaster* Module); static void OnStreamDeleted(RTMPModuleBroadcaster* Module); static void OnStreamError(RTMPModuleBroadcaster* Module); static void OnStopPublish(RTMPModuleBroadcaster* Module); static void OnStreamBandwidthChanged(RTMPModuleBroadcaster* Module, unsigned long Bandwidth, int32 QueueWasEmpty); void OnConnectionErrorImpl(RTMPModuleConnect* Module, RTMPEvent Evt, void* RejectInfoObj); void OnConnectionSuccessImpl(RTMPModuleConnect* Module); void OnStreamPublishedImpl(RTMPModuleBroadcaster* Module); void OnStreamDeletedImpl(RTMPModuleBroadcaster* Module); void OnStreamErrorImpl(RTMPModuleBroadcaster* Module); void OnStopPublishImpl(RTMPModuleBroadcaster* Module); void OnStreamBandwidthChangedImpl(RTMPModuleBroadcaster* Module, uint32 Bandwidth, bool bQueueWasEmpty); static RawData* GetAvccHeader(const TArrayView& DataView, const uint8** OutPpsEnd); static RawData* GetVideoPacket(const TArrayView& Data, bool bVideoKeyframe, const uint8* DataBegin); void InjectVideo(uint32 TimestampMs, const TArrayView& DataView, bool bIsKeyFrame); void InjectAudio(uint32 TimestampMs, const TArrayView& DataView); template static const uint8* Bytes(T& t) { return reinterpret_cast(&t); } // Custom log function for the IBM librayr static void CustomLogMsg(const char* Msg); // Utility functions to log an error if we are in the wrong state bool CheckState(const wchar_t* FuncName, EState ExpectedState); bool CheckState(const wchar_t* FuncName, EState ExpectedStateMin, EState ExpectedStateMax); static FIbmLiveStreaming* Singleton; // Initialization parameters struct { FString ClientId; FString ClientSecret; FString Channel; uint32 AudioSampleRate; uint32 AudioNumChannels; uint32 AudioBitrate; } Config; FIngest Ingest; TAtomic State = EState::None; TAtomic AudioPacketsSent = 0; TAtomic VideoPacketsSent = 0; FTimespan LiveStreamStartTimespan; uint32 FpsCalculationStartVideoPackets = 0; double FpsCalculationStartTime = 0; struct { RTMPClient* Client = nullptr; RTMPModuleBroadcaster* BroadcasterModule = nullptr; RTMPModuleConnect* ConnectModule = nullptr; } Ctx; FCriticalSection CtxMutex; // Helper function that locks Ctx and make the IBM call void QueueFrame(RTMPContentType FrameType, RawData* Pkt, uint32 TimestampMs); }; #endif // WITH_IBMRTMPINGEST