init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled
This commit is contained in:
275
Telegram/ThirdParty/tgcalls/macos/Sources/MediaStreaming.mm
vendored
Normal file
275
Telegram/ThirdParty/tgcalls/macos/Sources/MediaStreaming.mm
vendored
Normal file
@@ -0,0 +1,275 @@
|
||||
#import <TgVoipWebrtc/MediaStreaming.h>
|
||||
|
||||
#import "MediaUtils.h"
|
||||
|
||||
#include "StaticThreads.h"
|
||||
#include "group/StreamingMediaContext.h"
|
||||
|
||||
#include "api/video/video_sink_interface.h"
|
||||
#include "sdk/objc/native/src/objc_frame_buffer.h"
|
||||
#include "api/video/video_frame.h"
|
||||
|
||||
#import "components/video_frame_buffer/RTCCVPixelBuffer.h"
|
||||
#import "platform/darwin/TGRTCCVPixelBuffer.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace {
|
||||
|
||||
class BroadcastPartTaskImpl : public tgcalls::BroadcastPartTask {
|
||||
public:
|
||||
BroadcastPartTaskImpl(id<OngoingGroupCallBroadcastPartTask> task) {
|
||||
_task = task;
|
||||
}
|
||||
|
||||
virtual ~BroadcastPartTaskImpl() {
|
||||
}
|
||||
|
||||
virtual void cancel() override {
|
||||
[_task cancel];
|
||||
}
|
||||
|
||||
private:
|
||||
id<OngoingGroupCallBroadcastPartTask> _task;
|
||||
};
|
||||
|
||||
class VideoSinkAdapter : public rtc::VideoSinkInterface<webrtc::VideoFrame> {
|
||||
public:
|
||||
VideoSinkAdapter(void (^frameReceived)(webrtc::VideoFrame const &)) {
|
||||
_frameReceived = [frameReceived copy];
|
||||
}
|
||||
|
||||
void OnFrame(const webrtc::VideoFrame& nativeVideoFrame) override {
|
||||
@autoreleasepool {
|
||||
if (_frameReceived) {
|
||||
_frameReceived(nativeVideoFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void (^_frameReceived)(webrtc::VideoFrame const &);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@interface MediaStreamingVideoSink : NSObject {
|
||||
std::shared_ptr<VideoSinkAdapter> _adapter;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation MediaStreamingVideoSink
|
||||
|
||||
- (instancetype)initWithSink:(void (^_Nonnull)(CallVideoFrameData * _Nonnull))sink {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
void (^storedSink)(CallVideoFrameData * _Nonnull) = [sink copy];
|
||||
|
||||
_adapter.reset(new VideoSinkAdapter(^(webrtc::VideoFrame const &videoFrame) {
|
||||
id<CallVideoFrameBuffer> mappedBuffer = nil;
|
||||
|
||||
bool mirrorHorizontally = false;
|
||||
bool mirrorVertically = false;
|
||||
|
||||
if (videoFrame.video_frame_buffer()->type() == webrtc::VideoFrameBuffer::Type::kNative) {
|
||||
id<RTC_OBJC_TYPE(RTCVideoFrameBuffer)> nativeBuffer = static_cast<webrtc::ObjCFrameBuffer *>(videoFrame.video_frame_buffer().get())->wrapped_frame_buffer();
|
||||
if ([nativeBuffer isKindOfClass:[RTC_OBJC_TYPE(RTCCVPixelBuffer) class]]) {
|
||||
RTCCVPixelBuffer *pixelBuffer = (RTCCVPixelBuffer *)nativeBuffer;
|
||||
mappedBuffer = [[CallVideoFrameNativePixelBuffer alloc] initWithPixelBuffer:pixelBuffer.pixelBuffer];
|
||||
}
|
||||
if ([nativeBuffer isKindOfClass:[TGRTCCVPixelBuffer class]]) {
|
||||
if (((TGRTCCVPixelBuffer *)nativeBuffer).shouldBeMirrored) {
|
||||
switch (videoFrame.rotation()) {
|
||||
case webrtc::kVideoRotation_0:
|
||||
case webrtc::kVideoRotation_180:
|
||||
mirrorHorizontally = true;
|
||||
break;
|
||||
case webrtc::kVideoRotation_90:
|
||||
case webrtc::kVideoRotation_270:
|
||||
mirrorVertically = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (videoFrame.video_frame_buffer()->type() == webrtc::VideoFrameBuffer::Type::kNV12) {
|
||||
rtc::scoped_refptr<webrtc::NV12BufferInterface> nv12Buffer(static_cast<webrtc::NV12BufferInterface *>(videoFrame.video_frame_buffer().get()));
|
||||
|
||||
mappedBuffer = [[CallVideoFrameNV12Buffer alloc] initWithBuffer:nv12Buffer];
|
||||
} else if (videoFrame.video_frame_buffer()->type() == webrtc::VideoFrameBuffer::Type::kI420) {
|
||||
rtc::scoped_refptr<webrtc::I420BufferInterface> i420Buffer(static_cast<webrtc::I420BufferInterface *>(videoFrame.video_frame_buffer().get()));
|
||||
|
||||
mappedBuffer = [[CallVideoFrameI420Buffer alloc] initWithBuffer:i420Buffer];
|
||||
}
|
||||
|
||||
if (storedSink && mappedBuffer) {
|
||||
storedSink([[CallVideoFrameData alloc] initWithBuffer:mappedBuffer frame:videoFrame mirrorHorizontally:mirrorHorizontally mirrorVertically:mirrorVertically]);
|
||||
}
|
||||
}));
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (std::shared_ptr<rtc::VideoSinkInterface<webrtc::VideoFrame>>)sink {
|
||||
return _adapter;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@interface MediaStreamingContext () {
|
||||
id<OngoingCallThreadLocalContextQueueWebrtc> _queue;
|
||||
|
||||
id<OngoingGroupCallBroadcastPartTask> _Nonnull (^ _Nonnull _requestCurrentTime)(void (^ _Nonnull)(int64_t));
|
||||
id<OngoingGroupCallBroadcastPartTask> _Nonnull (^ _Nonnull _requestAudioBroadcastPart)(int64_t, int64_t, void (^ _Nonnull)(OngoingGroupCallBroadcastPart * _Nullable));
|
||||
id<OngoingGroupCallBroadcastPartTask> _Nonnull (^ _Nonnull _requestVideoBroadcastPart)(int64_t, int64_t, int32_t, OngoingGroupCallRequestedVideoQuality, void (^ _Nonnull)(OngoingGroupCallBroadcastPart * _Nullable));
|
||||
|
||||
std::unique_ptr<tgcalls::StreamingMediaContext> _context;
|
||||
|
||||
int _nextSinkId;
|
||||
NSMutableDictionary<NSNumber *, MediaStreamingVideoSink *> *_sinks;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation MediaStreamingContext
|
||||
|
||||
- (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue
|
||||
requestCurrentTime:(id<OngoingGroupCallBroadcastPartTask> _Nonnull (^ _Nonnull)(void (^ _Nonnull)(int64_t)))requestCurrentTime
|
||||
requestAudioBroadcastPart:(id<OngoingGroupCallBroadcastPartTask> _Nonnull (^ _Nonnull)(int64_t, int64_t, void (^ _Nonnull)(OngoingGroupCallBroadcastPart * _Nullable)))requestAudioBroadcastPart
|
||||
requestVideoBroadcastPart:(id<OngoingGroupCallBroadcastPartTask> _Nonnull (^ _Nonnull)(int64_t, int64_t, int32_t, OngoingGroupCallRequestedVideoQuality, void (^ _Nonnull)(OngoingGroupCallBroadcastPart * _Nullable)))requestVideoBroadcastPart {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_queue = queue;
|
||||
|
||||
_requestCurrentTime = [requestCurrentTime copy];
|
||||
_requestAudioBroadcastPart = [requestAudioBroadcastPart copy];
|
||||
_requestVideoBroadcastPart = [requestVideoBroadcastPart copy];
|
||||
|
||||
_sinks = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
}
|
||||
|
||||
- (void)resetContext {
|
||||
tgcalls::StreamingMediaContext::StreamingMediaContextArguments arguments;
|
||||
arguments.threads = tgcalls::StaticThreads::getThreads();
|
||||
arguments.isUnifiedBroadcast = true;
|
||||
arguments.requestCurrentTime = [requestCurrentTime = _requestCurrentTime](std::function<void(int64_t)> completion) -> std::shared_ptr<tgcalls::BroadcastPartTask> {
|
||||
id<OngoingGroupCallBroadcastPartTask> task = requestCurrentTime(^(int64_t result) {
|
||||
completion(result);
|
||||
});
|
||||
return std::make_shared<BroadcastPartTaskImpl>(task);
|
||||
};
|
||||
arguments.requestAudioBroadcastPart = nullptr;
|
||||
arguments.requestVideoBroadcastPart = [requestVideoBroadcastPart = _requestVideoBroadcastPart](int64_t timestampMilliseconds, int64_t durationMilliseconds, int32_t channelId, tgcalls::VideoChannelDescription::Quality quality, std::function<void(tgcalls::BroadcastPart &&)> completion) -> std::shared_ptr<tgcalls::BroadcastPartTask> {
|
||||
OngoingGroupCallRequestedVideoQuality mappedQuality;
|
||||
switch (quality) {
|
||||
case tgcalls::VideoChannelDescription::Quality::Thumbnail: {
|
||||
mappedQuality = OngoingGroupCallRequestedVideoQualityThumbnail;
|
||||
break;
|
||||
}
|
||||
case tgcalls::VideoChannelDescription::Quality::Medium: {
|
||||
mappedQuality = OngoingGroupCallRequestedVideoQualityMedium;
|
||||
break;
|
||||
}
|
||||
case tgcalls::VideoChannelDescription::Quality::Full: {
|
||||
mappedQuality = OngoingGroupCallRequestedVideoQualityFull;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
mappedQuality = OngoingGroupCallRequestedVideoQualityThumbnail;
|
||||
break;
|
||||
}
|
||||
}
|
||||
id<OngoingGroupCallBroadcastPartTask> task = requestVideoBroadcastPart(timestampMilliseconds, durationMilliseconds, channelId, mappedQuality, ^(OngoingGroupCallBroadcastPart * _Nullable part) {
|
||||
tgcalls::BroadcastPart parsedPart;
|
||||
parsedPart.timestampMilliseconds = part.timestampMilliseconds;
|
||||
|
||||
parsedPart.responseTimestamp = part.responseTimestamp;
|
||||
|
||||
tgcalls::BroadcastPart::Status mappedStatus;
|
||||
switch (part.status) {
|
||||
case OngoingGroupCallBroadcastPartStatusSuccess: {
|
||||
mappedStatus = tgcalls::BroadcastPart::Status::Success;
|
||||
break;
|
||||
}
|
||||
case OngoingGroupCallBroadcastPartStatusNotReady: {
|
||||
mappedStatus = tgcalls::BroadcastPart::Status::NotReady;
|
||||
break;
|
||||
}
|
||||
case OngoingGroupCallBroadcastPartStatusResyncNeeded: {
|
||||
mappedStatus = tgcalls::BroadcastPart::Status::ResyncNeeded;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
mappedStatus = tgcalls::BroadcastPart::Status::NotReady;
|
||||
break;
|
||||
}
|
||||
}
|
||||
parsedPart.status = mappedStatus;
|
||||
|
||||
parsedPart.data.resize(part.oggData.length);
|
||||
[part.oggData getBytes:parsedPart.data.data() length:part.oggData.length];
|
||||
|
||||
completion(std::move(parsedPart));
|
||||
});
|
||||
return std::make_shared<BroadcastPartTaskImpl>(task);
|
||||
};
|
||||
|
||||
arguments.updateAudioLevel = nullptr;
|
||||
|
||||
_context = std::make_unique<tgcalls::StreamingMediaContext>(std::move(arguments));
|
||||
|
||||
for (MediaStreamingVideoSink *storedSink in _sinks.allValues) {
|
||||
_context->addVideoSink("unified", [storedSink sink]);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)start {
|
||||
[self resetContext];
|
||||
}
|
||||
|
||||
- (void)stop {
|
||||
_context.reset();
|
||||
}
|
||||
|
||||
- (GroupCallDisposable * _Nonnull)addVideoOutput:(void (^_Nonnull)(CallVideoFrameData * _Nonnull))sink {
|
||||
int sinkId = _nextSinkId;
|
||||
_nextSinkId += 1;
|
||||
|
||||
MediaStreamingVideoSink *storedSink = [[MediaStreamingVideoSink alloc] initWithSink:sink];
|
||||
_sinks[@(sinkId)] = storedSink;
|
||||
|
||||
if (_context) {
|
||||
_context->addVideoSink("unified", [storedSink sink]);
|
||||
}
|
||||
|
||||
__weak MediaStreamingContext *weakSelf = self;
|
||||
id<OngoingCallThreadLocalContextQueueWebrtc> queue = _queue;
|
||||
return [[GroupCallDisposable alloc] initWithBlock:^{
|
||||
[queue dispatch:^{
|
||||
__strong MediaStreamingContext *strongSelf = weakSelf;
|
||||
if (!strongSelf) {
|
||||
return;
|
||||
}
|
||||
|
||||
[strongSelf->_sinks removeObjectForKey:@(sinkId)];
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)getAudio:(int16_t * _Nonnull)audioSamples numSamples:(NSInteger)numSamples numChannels:(NSInteger)numChannels samplesPerSecond:(NSInteger)samplesPerSecond {
|
||||
if (_context) {
|
||||
_context->getAudio(audioSamples, (size_t)numSamples, (size_t)numChannels, (uint32_t)samplesPerSecond);
|
||||
} else {
|
||||
memset(audioSamples, 0, numSamples * numChannels * sizeof(int16_t));
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user