IOS에서 ReplayKit 및 RTC 사용 방법

날로 복잡해지는 생방송 장면에서 만약에 당신도 어떤 게임 아나운서의 팬이라면 생방송 방식이 낯설지 않을 것이다. 그것은 바로 우리가 오늘 이야기하고자 하는 스크린 공유이다.
생방송 장면에서의 화면 공유는 현재 모니터가 보여준 화면을 원단에 공유해야 할 뿐만 아니라 소리를 전송해야 한다. 응용된 소리와 아나운서의 소리를 포함한다.이 두 가지 수요를 감안하여 우리는 스크린 공유를 하는 생방송에 필요한 미디어 흐름을 간단하게 분석할 수 있다.
  • 모니터 화면의 비디오 흐름
  • 소리를 응용한 오디오 흐름
  • 아나운서 소리의 오디오 흐름
  • ReplayKit은 iOS 시스템에서 화면 녹화를 위해 애플이 제공하는 프레임워크입니다.
    우선 애플이 화면 녹화에 사용할 ReplayKit의 데이터 리셋 인터페이스를 살펴보자.
    
    override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) {
            DispatchQueue.main.async {
                switch sampleBufferType {
                case .video:
                    AgoraUploader.sendVideoBuffer(sampleBuffer)
                case .audioApp:
                    AgoraUploader.sendAudioAppBuffer(sampleBuffer)
                case .audioMic:
                    AgoraUploader.sendAudioMicBuffer(sampleBuffer)
                @unknown default:
                    break
                }
            }
        }
    매거진sampleBuffer Type에서 우리는 우리가 상술한 미디어 흐름에 대한 수요에 딱 부합된다는 것을 어렵지 않게 알 수 있다.

    비디오 형식

    
    guard let videoFrame = CMSampleBufferGetImageBuffer(sampleBuffer) else {
        return
    }
            
    let type = CVPixelBufferGetPixelFormatType(videoFrame)
    
    type = kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
    CVPixelBufferGetPixelFormatType을 통해 프레임당 비디오 형식은 yuv420입니다.

    프레임 속도


    인쇄 인터페이스의 리셋 횟수를 통해 초당 얻을 수 있는 영상 프레임이 30회, 즉 프레임 속도가 30이라는 것을 알 수 있다.
    포맷과 프레임 속도는 아고라 RTC가 수신할 수 있는 범위에 부합되기 때문에 아고라 RTC의pushExternal VideoFrame를 통해 영상을 원격으로 공유할 수 있습니다.
    
    agoraKit.pushExternalVideoFrame(frame)

    작은 지식을 삽입하다


    디스플레이에 표시되는 프레임은 일반적으로 이중 캐시 또는 3 캐시로 흔히 볼 수 있는 프레임 캐시 영역에서 나온다.화면이 프레임을 표시한 후 수직 동기화 신호(V-Sync)를 보내서 프레임 캐시 영역이 다음 프레임의 캐시로 전환되는 것을 알려주고 모니터가 새 프레임 데이터를 읽어서 표시합니다.
    이 프레임 캐시 구역은 시스템 등급으로 일반 개발자들은 읽고 쓸 수 없습니다.그러나 애플이 자체적으로 제공한 녹화 프레임워크인 ReplayKit가 이미 렌더링되어 디스플레이에 사용될 프레임을 직접 읽을 수 있고 이 과정이 렌더링 프로세스에 영향을 주지 않아 프레임이 떨어지면 ReplayKit에 데이터를 리셋하는 렌더링 과정을 한 번 줄일 수 있다.
    오디오
    ReplayKit가 제공할 수 있는 오디오는 두 가지로 마이크에 녹음된 오디오 흐름과 현재 응답하는 응용 프로그램에서 재생된 오디오 흐름으로 나뉜다.(다음 글은 전자를 AudioMic, 후자를 AudioApp이라고 함)
    아래의 두 줄 코드를 통해 오디오 형식을 얻을 수 있습니다
    
    CMAudioFormatDescriptionRef format = CMSampleBufferGetFormatDescription(sampleBuffer);
    const AudioStreamBasicDescription *description = CMAudioFormatDescriptionGetStreamBasicDescription(format);

    AudioApp


    AudioApp은 모델에 따라 채널 수가 달라집니다.예를 들어 아이패드나 아이폰7 이하 모델에는 이중 채널 재생 장치가 없다. 이때 오디오 앱의 데이터는 단일 채널이고 반대로 이중 채널이다.
    샘플링 확률은 일부 시험된 기종 중 44100이지만 테스트되지 않은 기종은 다른 샘플링 확률을 배제하지 않는다.

    AudioMic


    AudioMic는 테스트된 기종에서 샘플링 확률은 32000이고 채널 수는 단채널이다.

    오디오 사전 처리


    만약 우리가 Audio App과 Audio Mic를 두 개의 오디오 흐름으로 보낸다면, 데이터는 틀림없이 한 개의 오디오 흐름보다 클 것이다.우리는 한 개의 오디오 흐름의 유량을 절약하기 위해서 이 두 개의 오디오 흐름을 혼합 (융합) 해야 한다.
    그러나 상술한 바를 통해 우리는 두 개의 오디오 흐름의 형식이 다르고 기종에 따라 다른 형식이 나타날지 장담하기 어렵지 않다.테스트 과정에서 OS 버전에 따라 매번 리셋된 오디오 데이터의 길이도 변하는 것을 발견했다.그러면 우리는 두 개의 오디오 흐름을 믹스하기 전에 ReplayKit가 제시한 각종 형식에 대응하기 위해 형식을 통일해야 한다.그래서 우리는 다음과 같은 몇 가지 중요한 절차를 채택했다.
    
    if (channels == 1) {
        int16_t* intData = (int16_t*)dataPointer;
        int16_t newBuffer[totalSamples * 2];
                
        for (int i = 0; i < totalSamples; i++) {
            newBuffer[2 * i] = intData[i];
            newBuffer[2 * i + 1] = intData[i];
        }
        totalSamples *= 2;
        memcpy(dataPointer, newBuffer, sizeof(int16_t) * totalSamples);
        totalBytes *= 2;
        channels = 2;
    }
    AudioMic이든 AudioApp이든 들어오는 흐름이 단성도라면 우리는 그것을 쌍성도로 전환한다.
    
    if (sampleRate != resampleRate) {
        int inDataSamplesPer10ms = sampleRate / 100;
        int outDataSamplesPer10ms = (int)resampleRate / 100;
    
        int16_t* intData = (int16_t*)dataPointer;
    
        switch (type) {
            case AudioTypeApp:
                totalSamples = resampleApp(intData, dataPointerSize, totalSamples,
                                           inDataSamplesPer10ms, outDataSamplesPer10ms, channels, sampleRate, (int)resampleRate);
                break;
            case AudioTypeMic:
                totalSamples = resampleMic(intData, dataPointerSize, totalSamples,
                                           inDataSamplesPer10ms, outDataSamplesPer10ms, channels, sampleRate, (int)resampleRate);
                break;
        }
    
        totalBytes = totalSamples * sizeof(int16_t);
    }
    AudioMic이든 Audio App이든 들어오는 샘플링 확률이 48000이 아니라면 우리는 그것들을 48000으로 다시 샘플링한다.
    
    memcpy(appAudio + appAudioIndex, dataPointer, totalBytes);
    appAudioIndex += totalSamples;
    
    memcpy(micAudio + micAudioIndex, dataPointer, totalBytes);
    micAudioIndex += totalSamples;
    첫 번째 단계와 두 번째 단계를 통해 우리는 두 개의 오디오 흐름이 모두 같은 오디오 형식임을 보증했다.그러나 ReplayKit는 한 번에 하나의 데이터로 리셋되기 때문에 믹싱하기 전에 우리는 두 개의 캐시 구역으로 이 두 개의 흐름 데이터를 저장해야 한다.
    
    int64_t mixIndex = appAudioIndex > micAudioIndex ? micAudioIndex : appAudioIndex;
            
    int16_t pushBuffer[appAudioIndex];
            
    memcpy(pushBuffer, appAudio, appAudioIndex * sizeof(int16_t));
            
    for (int i = 0; i < mixIndex; i ++) {
       pushBuffer[i] = (appAudio[i] + micAudio[i]) / 2;
    }
    ReplayKit는 마이크 녹화를 켤지 여부를 선택할 수 있기 때문에 마이크 녹화를 닫을 때 Audio App 오디오 흐름이 하나밖에 없습니다.그래서 우리는 이 흐름을 위주로 AudioMic 캐시 구역의 데이터 길이를 읽고 두 캐시 구역의 데이터 길이를 비교한 다음에 가장 작은 데이터 길이를 우리의 혼합 길이로 한다.믹스 길이의 두 캐시 구역의 데이터를 융합시켜 믹스 후의 데이터를 얻어 새로운 믹스 캐시 구역(또는AudioApp 캐시 구역)에 기록한다.
    
    [AgoraAudioProcessing pushAudioFrame:(*unsigned* *char* *)pushBuffer
                                       withFrameSize:appAudioIndex * *sizeof*(int16_t)];
    마지막으로 우리는 이 믹스 후의 데이터를 아고라 RTC의 C++ 녹화 리셋 인터페이스에 복사하면 이때 마이크가 녹음한 소리와 응용 프로그램이 재생한 소리를 원거리로 전송할 수 있다.
    음성 영상 흐름에 대한 처리를 통해 아고라 RTC SDK와 결합하면 우리는 스크린으로 생방송 장면을 공유하는 실현을 완성했다.
    다음은 IOS에서 ReplayKit와 RTC를 사용하는 방법에 대한 상세한 내용입니다. IOS에서 ReplayKit와 RTC를 사용하는 방법에 대한 더 많은 자료는 저희의 기타 관련 글을 참고하세요!

    좋은 웹페이지 즐겨찾기