WebRTC オーディオおよびビデオ通話 - ローカル ビデオおよびアルバム ビデオ ファイルの RTC ライブ ブロードキャスト

WebRTC オーディオおよびビデオ通話 - ローカル ビデオおよびアルバム ビデオ ファイルの RTC ライブ ブロードキャスト

WebRTC オーディオおよびビデオ通話 - RTC ライブ ブロードキャストのローカル ビデオ ファイルのレンダリングは次のとおりです。
ここに画像の説明を挿入

WebRTC オーディオおよびビデオ コール - RTC がローカル ビデオ ファイルをブロードキャストする場合、AVPlayer および CADisplayLink が使用されます。

1. AVPlayer を通じてローカルビデオを再生します。

  • AVプレーヤーとは何ですか?

AVPlayer は AVFoundation フレームワークに基づいたクラスで、最下層に非常に近く、強力な柔軟性を持ち、ビデオの再生スタイルをカスタマイズできます。

  • AVPlayerLayerとは何ですか?

AVPlayerLayer は、ビデオ再生時の画面表示レイヤーです。

  • CADisplayLinkとは何ですか?

NSTimer と同様に、CADisplayLink はタイマーです。ただし、CADisplayLink は常に画面のリフレッシュ レートと一致します (多くの場合、CADisplayLink は画面のフレーム レートを検出するために使用されます)。

  • AVPlayerItemVideoOutput とは何ですか?

AVPlayerItemVideoOutput は PlayerItem ビデオの出力であり、ビデオの CVPixelBufferRef は AVPlayerItemVideoOutput を通じて取得できます。

以下は、ローカルビデオ再生時に ossrs と組み合わせて WebRTC ライブ ブロードキャストを実装する方法です。

ローカルビデオを再生するための AVPlayer 設定

 AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithURL:[NSURL fileURLWithPath:videoPath]];
 (void)reloadPlayItem:(AVPlayerItem *)playerItem {
    
    
    self.playerItem = playerItem;
    [self initPlayerVideoOutput];
        
    self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
}

再生表示の設定を開始する AVPlayerLayer

- (void)startPlay {
    
    
    if (self.isPlaying) {
    
    
        return;
    }
    
    AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
    self.playerLayer = playerLayer;
    self.playerLayer.backgroundColor = [UIColor clearColor].CGColor;
    self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    
    [self.superView.layer addSublayer:self.playerLayer];
    self.playerLayer.frame = self.superView.bounds;

    [self.player seekToTime:CMTimeMake(0, 1)];
    [self.player play];
    [self startAnimating];
}

KVOを通じてプレーヤーのステータスを監視

/**
 *  通过KVO监控播放器状态
 */
- (void)observeValueForKeyPath:(NSString *)keyPath {
    
    
    DebugLog(@"observeValueForKeyPath:%@", keyPath);
    
    AVPlayerItem *videoItem = self.playerItem;
    if ([keyPath isEqualToString:@"timeControlStatus"]) {
    
    
        
        /**
         typedef NS_ENUM(NSInteger, AVPlayerTimeControlStatus) {
             AVPlayerTimeControlStatusPaused = 0,
             AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate = 1,
             AVPlayerTimeControlStatusPlaying = 2
         } API_AVAILABLE(macos(10.12), ios(10.0), tvos(10.0), watchos(3.0));
         */
        // 监听播放器timeControlStatus 指示当前是否正在播放,无限期暂停播放,或在等待适当的网络条件时暂停播放
        if (@available(iOS 10.0, *)) {
    
    
            switch (self.player.timeControlStatus) {
    
    
                case AVPlayerTimeControlStatusPaused: {
    
    
                    NSLog(@"AVPlayerTimeControlStatusPaused");
                    // 暂停
                    self.isPlaying = NO;
                }
                    break;
                case AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate: {
    
    
                    NSLog(@"AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate");
                    // 等待
                }
                    break;
                case AVPlayerTimeControlStatusPlaying: {
    
    
                    NSLog(@"AVPlayerTimeControlStatusPlaying");
                    // 播放
                    self.isPlaying = YES;
                }
                    break;
                default:
                    break;
            }
        } else {
    
    
            // Fallback on earlier versions
        }
    }
}

キーの設定 AVPlayerItemVideoOutput

- (void)initPlayerVideoOutput {
    
    
    //输出yuv 420格式
    NSDictionary *pixBuffAttributes = @{
    
    (id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)};
    AVPlayerItemVideoOutput *output = [[AVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes:pixBuffAttributes];
    [self.playerItem addOutput:output];
    self.playerItemVideoOutput = output;
    
    [self.playerItemVideoOutput setDelegate:self queue:dispatch_get_main_queue()];
    
    // 如果将 AVPlayerItemVideoOutput 类的输出(对于 suppressesPlayerRendering 的值为 YES)添加到 AVPlayerItem,则该项目的视频媒体将不会由 AVPlayer 呈现,而音频媒体、字幕媒体和其他类型的媒体(如果存在) , 将被渲染。
    self.playerItemVideoOutput.suppressesPlayerRendering = NO;
}

その後、displayLink を開いて AVPlayerItemVideoOutput をリアルタイムで呼び出し、ビデオ画面 CVPixelBufferRef を取得します。

#pragma mark - DisplayLink
- (void)startDisplayLink {
    
    
    if (self.displayLink) {
    
    
        return;
    }
    self.displayLink = [CADisplayLink displayLinkWithTarget:[YYWeakProxy proxyWithTarget:self]
     selector:@selector(handleDisplayLink:)];
    [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    // self.displayLink.preferredFramesPerSecond = 2;
    self.displayLink.paused = NO;
}

- (void)handleDisplayLink:(CADisplayLink *)displayLink {
    
    
    //do something
    CMTime outputItemTime = kCMTimeInvalid;
    CFTimeInterval nextVSync = ([displayLink timestamp] + [displayLink duration]);
    outputItemTime = [[self playerItemVideoOutput] itemTimeForHostTime:nextVSync];
    if ([[self playerItemVideoOutput] hasNewPixelBufferForItemTime:outputItemTime]) {
    
    
        CVPixelBufferRef pixelBuffer = NULL;
        pixelBuffer = [[self playerItemVideoOutput] copyPixelBufferForItemTime:outputItemTime itemTimeForDisplay:NULL];
        // ..... do something with pixbuffer
        if (self.delegate && [self.delegate respondsToSelector:@selector(videoLivePlayerPixelBufferRef:)]) {
    
    
            [self.delegate videoLivePlayerPixelBufferRef:pixelBuffer];
        }
        
        if (pixelBuffer != NULL) {
    
    
            CFRelease(pixelBuffer);
        }
    }
}

- (void)stopDisplayLink {
    
    
    [self.displayLink invalidate];
    self.displayLink = nil;
}

このように、再生プロセス中に CADisplayLink を通じて CVPixelBufferRef が取得され、WebRTC がライブ ブロードキャストに使用されます。

フルビデオ再生中に CVPixelBufferRef を取得するコードは次のとおりです。

SDVideoLivePlayer.h

#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>
#import <AVFoundation/AVFoundation.h>

@protocol SDVideoLivePlayerDelegate;
@interface SDVideoLivePlayer : NSObject

@property (nonatomic, strong) UIView *superView;

@property (nonatomic, weak) id<SDVideoLivePlayerDelegate> delegate;

- (instancetype)initWithSuperView:(UIView *)superView;

- (void)reloadPlayItem:(AVPlayerItem *)playerItem;

/// 开始播放
- (void)startPlay;

/// 结束播放
- (void)stopPlay;

@end

@protocol SDVideoLivePlayerDelegate <NSObject>

- (void)videoLivePlayerPixelBufferRef:(CVPixelBufferRef)pixelBufferRef;

@end

SDVideoLivePlayer.m

#import "SDVideoLivePlayer.h"

@interface SDVideoLivePlayer ()<AVPlayerItemOutputPullDelegate> {
    
    

}

@property (nonatomic, strong) AVPlayer *player;
@property (nonatomic, strong) AVPlayerLayer *playerLayer;
@property (nonatomic, strong) AVPlayerItem *playerItem;
@property (nonatomic, assign) BOOL isPlaying;
@property (nonatomic, strong) AVPlayerItemVideoOutput *playerItemVideoOutput;
@property (nonatomic, strong) CADisplayLink *displayLink;

@end

@implementation SDVideoLivePlayer

- (instancetype)initWithSuperView:(UIView *)superView
{
    
    
    self = [super init];
    if (self) {
    
    
        self.superView = superView;
        self.isPlaying = NO;
        [self addNotifications];
        [self initPlayerVideoOutput];
    }
    return self;
}

- (void)initPlayerVideoOutput {
    
    
    //输出yuv 420格式
    NSDictionary *pixBuffAttributes = @{
    
    (id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)};
    AVPlayerItemVideoOutput *output = [[AVPlayerItemVideoOutput alloc] initWithPixelBufferAttributes:pixBuffAttributes];
    [self.playerItem addOutput:output];
    self.playerItemVideoOutput = output;
    
    [self.playerItemVideoOutput setDelegate:self queue:dispatch_get_main_queue()];
    
    // 如果将 AVPlayerItemVideoOutput 类的输出(对于 suppressesPlayerRendering 的值为 YES)添加到 AVPlayerItem,则该项目的视频媒体将不会由 AVPlayer 呈现,而音频媒体、字幕媒体和其他类型的媒体(如果存在) , 将被渲染。
    self.playerItemVideoOutput.suppressesPlayerRendering = NO;
}

- (void)reloadPlayItem:(AVPlayerItem *)playerItem {
    
    
    
    self.playerItem = playerItem;
    [self initPlayerVideoOutput];
        
    self.player = [AVPlayer playerWithPlayerItem:self.playerItem];
}

- (void)startPlay {
    
    
    if (self.isPlaying) {
    
    
        return;
    }
    
    AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.player];
    self.playerLayer = playerLayer;
    self.playerLayer.backgroundColor = [UIColor clearColor].CGColor;
    self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    
    [self.superView.layer addSublayer:self.playerLayer];
    self.playerLayer.frame = self.superView.bounds;

    [self.player seekToTime:CMTimeMake(0, 1)];
    [self.player play];
    [self startAnimating];
}

- (void)stopPlay {
    
    
    self.isPlaying = NO;
    [self.player pause];
    [self stopAnimating];
}

/**
 *  通过KVO监控播放器状态
 */
- (void)observeValueForKeyPath:(NSString *)keyPath {
    
    
    DebugLog(@"observeValueForKeyPath:%@", keyPath);
    
    AVPlayerItem *videoItem = self.playerItem;
    if ([keyPath isEqualToString:@"timeControlStatus"]) {
    
    
        
        /**
         typedef NS_ENUM(NSInteger, AVPlayerTimeControlStatus) {
             AVPlayerTimeControlStatusPaused = 0,
             AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate = 1,
             AVPlayerTimeControlStatusPlaying = 2
         } API_AVAILABLE(macos(10.12), ios(10.0), tvos(10.0), watchos(3.0));
         */
        // 监听播放器timeControlStatus 指示当前是否正在播放,无限期暂停播放,或在等待适当的网络条件时暂停播放
        if (@available(iOS 10.0, *)) {
    
    
            switch (self.player.timeControlStatus) {
    
    
                case AVPlayerTimeControlStatusPaused: {
    
    
                    NSLog(@"AVPlayerTimeControlStatusPaused");
                    // 暂停
                    self.isPlaying = NO;
                }
                    break;
                case AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate: {
    
    
                    NSLog(@"AVPlayerTimeControlStatusWaitingToPlayAtSpecifiedRate");
                    // 等待
                }
                    break;
                case AVPlayerTimeControlStatusPlaying: {
    
    
                    NSLog(@"AVPlayerTimeControlStatusPlaying");
                    // 播放
                    self.isPlaying = YES;
                }
                    break;
                default:
                    break;
            }
        } else {
    
    
            // Fallback on earlier versions
        }
    }
}

- (void)audioSessionInterrupted:(NSNotification *)notification{
    
    
    //通知类型
    NSDictionary * info = notification.userInfo;
    if ([[info objectForKey:AVAudioSessionInterruptionTypeKey] integerValue] == 1) {
    
    
        [self.player pause];
    } else {
    
    
        [self.player play];
    }
}

- (void)startAnimating {
    
    
    [self startDisplayLink];
    self.displayLink.paused = NO;
}

- (void)stopAnimating {
    
    
    self.displayLink.paused = YES;
    [self stopDisplayLink];
}

- (void)pauseAnimating {
    
    
    self.displayLink.paused = YES;
    [self stopDisplayLink];
}

- (void)resumeAnimating {
    
    
    if (!self.displayLink) {
    
    
        [self startDisplayLink];
    }
    self.displayLink.paused = NO;
}

#pragma mark - DisplayLink
- (void)startDisplayLink {
    
    
    if (self.displayLink) {
    
    
        return;
    }
    self.displayLink = [CADisplayLink displayLinkWithTarget:[YYWeakProxy proxyWithTarget:self]
     selector:@selector(handleDisplayLink:)];
    [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    // self.displayLink.preferredFramesPerSecond = 2;
    self.displayLink.paused = NO;
}

- (void)handleDisplayLink:(CADisplayLink *)displayLink {
    
    
    //do something
    CMTime outputItemTime = kCMTimeInvalid;
    CFTimeInterval nextVSync = ([displayLink timestamp] + [displayLink duration]);
    outputItemTime = [[self playerItemVideoOutput] itemTimeForHostTime:nextVSync];
    if ([[self playerItemVideoOutput] hasNewPixelBufferForItemTime:outputItemTime]) {
    
    
        CVPixelBufferRef pixelBuffer = NULL;
        pixelBuffer = [[self playerItemVideoOutput] copyPixelBufferForItemTime:outputItemTime itemTimeForDisplay:NULL];
        // ..... do something with pixbuffer
        if (self.delegate && [self.delegate respondsToSelector:@selector(videoLivePlayerPixelBufferRef:)]) {
    
    
            [self.delegate videoLivePlayerPixelBufferRef:pixelBuffer];
        }
        
        if (pixelBuffer != NULL) {
    
    
            CFRelease(pixelBuffer);
        }
    }
}

- (void)stopDisplayLink {
    
    
    [self.displayLink invalidate];
    self.displayLink = nil;
}

#pragma mark - AVPlayerItemOutputPullDelegate
- (void)outputMediaDataWillChange:(AVPlayerItemOutput *)sender {
    
    
    [self stopPlay];
}

#pragma mark - Observers
- (void)addNotifications {
    
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(replay:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
    
    // 音频播放被中断
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioSessionInterrupted:) name:AVAudioSessionInterruptionNotification object:nil];
    
    __weak typeof(self) weakSelf = self;
    if (@available(iOS 10.0, *)) {
    
    
        [self.KVOController observe:self.player keyPath:@"timeControlStatus" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
    
    
            __strong typeof(weakSelf) strongSelf = weakSelf;
            [strongSelf observeValueForKeyPath:@"timeControlStatus"];
        }];
    }
}

- (void)removeNotifications {
    
    
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [self.KVOController unobserveAll];
}

- (void)replay:(NSNotification *)notification {
    
    
    if (notification.object == self.player.currentItem) {
    
    
        [self.player seekToTime:CMTimeMake(0, 1)];
        [self.player play];
    }
}

- (void)dealloc {
    
    
    [self removeNotifications];
}

@end

2. アルバムビデオを入手する

アルバム ビデオを取得するコードは次のとおりです。AVPlayerItem を取得するには、-(void)reloadPlayItem:(AVPlayerItem *)playerItem; を呼び出します。

- (void)startPlayAlbumVideo:(SDMediaModel *)mediaModel {
    
    
    if (!(mediaModel && mediaModel.phasset)) {
    
    
        return;
    }
    __weak typeof(self) weakSelf = self;
    [[PhotoKitManager shareInstance] requestPlayerItemForVideo:mediaModel.phasset completion:^(AVPlayerItem *playerItem) {
    
    
        __strong typeof(weakSelf) strongSelf = weakSelf;
        [strongSelf.videoLivePlayer reloadPlayItem:playerItem];
        [strongSelf.videoLivePlayer startPlay];
    } failure:^{
    
    
        __strong typeof(weakSelf) strongSelf = weakSelf;
    }];
}

3. RTC ライブビデオ用の WebRTC

CADisplayLinkはCVPixelBufferRefを取得し、ライブブロードキャストにはWebRTCを使用します。これは、事前に準備したビデオをライブブロードキャストすることに相当します。
GPUImage ビデオ呼び出しビデオ ビューティ フィルターを実装する前に、ここでは RTCVideoFrame を使用します。取得したビデオの CVPixelBufferRef を RTCVideoFrame にパッケージ化し、RTCVideoSource の DidCaptureVideoFrame メソッドを呼び出して最終的な効果を実現する必要があります。

RTCVideoSourceの設定は以下の通りです

- (RTCVideoTrack *)createVideoTrack {
    
    
    RTCVideoSource *videoSource = [self.factory videoSource];
    self.localVideoSource = videoSource;

    // 如果是模拟器
    if (TARGET_IPHONE_SIMULATOR) {
    
    
        if (@available(iOS 10, *)) {
    
    
            self.videoCapturer = [[RTCFileVideoCapturer alloc] initWithDelegate:self];
        } else {
    
    
            // Fallback on earlier versions
        }
    } else{
    
    
        self.videoCapturer = [[RTCCameraVideoCapturer alloc] initWithDelegate:self];
    }
    
    RTCVideoTrack *videoTrack = [self.factory videoTrackWithSource:videoSource trackId:@"video0"];
    
    return videoTrack;
}


- (void)createMediaSenders {
    
    
    if (!self.isPublish) {
    
    
        return;
    }
    
    NSString *streamId = @"stream";
    
    // Audio
    RTCAudioTrack *audioTrack = [self createAudioTrack];
    self.localAudioTrack = audioTrack;
    
    RTCRtpTransceiverInit *audioTrackTransceiver = [[RTCRtpTransceiverInit alloc] init];
    audioTrackTransceiver.direction = RTCRtpTransceiverDirectionSendOnly;
    audioTrackTransceiver.streamIds = @[streamId];
    
    [self.peerConnection addTransceiverWithTrack:audioTrack init:audioTrackTransceiver];
    
    // Video
    RTCVideoTrack *videoTrack = [self createVideoTrack];
    self.localVideoTrack = videoTrack;
    RTCRtpTransceiverInit *videoTrackTransceiver = [[RTCRtpTransceiverInit alloc] init];
    videoTrackTransceiver.direction = RTCRtpTransceiverDirectionSendOnly;
    videoTrackTransceiver.streamIds = @[streamId];
    [self.peerConnection addTransceiverWithTrack:videoTrack init:videoTrackTransceiver];
}

詳細については、iOS 側での ossrs 音声通話およびビデオ通話の呼び出しのセクションを参照してください。

取得したビデオのCVPixelBufferRefをRTCVideoFrameにカプセル化し、RTCVideoSourceのdidCaptureVideoFrameメソッドを呼び出します。

- (RTCVideoFrame *)webRTCClient:(WebRTCClient *)client videoPixelBufferRef:(CVPixelBufferRef)videoPixelBufferRef {
    
    
    RTCCVPixelBuffer *rtcPixelBuffer =
    [[RTCCVPixelBuffer alloc] initWithPixelBuffer:videoPixelBufferRef];
    RTCCVPixelBuffer *rtcPixelBuffer = [[RTCCVPixelBuffer alloc] initWithPixelBuffer:pixelBuffer];
    int64_t timeStampNs = CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer)) *
    1000000000;
    RTCVideoFrame *rtcVideoFrame = [[RTCVideoFrame alloc] initWithBuffer:rtcPixelBuffer                                   rotation:RTCVideoRotation_0                                      timeStampNs:timeStampNs];
                  
    return rtcVideoFrame;
}

RTCVideoSource を使用して DidCaptureVideoFrame を呼び出す

[self.localVideoSource キャプチャ:キャプチャ DidCaptureVideoFrame:frame];

その他
ossrs サービスを設定する前に、次を確認できます: https://blog.csdn.net/gloryFlow/article/details/132257196
ossrs の音声通話とビデオ通話を呼び出すために iOS を実装する前に、次を確認できます: https://blog. csdn.net/gloryFlow /article/details/132262724
WebRTC オーディオおよびビデオ通話で高解像度の画像が表示されない前に、次のことを確認できます。 https://blog.csdn.net/gloryFlow/article/details/132262724
SDP、確認できます: https://blog.csdn.net/gloryFlow/article/details/132263021
GPUImage ビデオ通話ビデオビューティー フィルター、確認できます: https://blog.csdn.net/gloryFlow/article/details/ 132265842

4. まとめ

WebRTC オーディオおよびビデオ通話 - ローカル ビデオ ファイルの RTC ライブ ストリーミング。AVPlayer は主にビデオの再生に使用され、AVPlayerItemVideoOutput が CVPixelBufferRef を取得し、処理された CVPixelBufferRef が RTCVideoFrame に生成され、WebRTC の localVideoSource に実装されている DidCaptureVideoFrame メソッドが呼び出されます。内容が多く、不正確な記述があるかもしれませんが、ご容赦ください。

この記事のアドレス: https://blog.csdn.net/gloryFlow/article/details/132267068

学習記録、日々改善を続けてください。

おすすめ

転載: blog.csdn.net/gloryFlow/article/details/132267068