iOS

通过阅读本文,您可以了解 iOS 输入外部音视频流的方法。

输入外部视频流

说明

SDK允许先推流然后开启外部视频输入,但这种情况下,默认开始推流时,先推送出的是本地原始采集源(摄像头或屏幕捕获)的视频数据,直到启用外部输入。

1. 调用 setExternalVideoSource 打开外部视频输入开关。

int ret = [DingRtcClient.instance.rtcEngine setExternalVideoSource:YES track:DingRtcVideoTrackCamera];

2. 调用 pushExternalVideoFrame 输入视频数据。

说明
  • 此处需要重新开启线程调用 pushExternalVideoFrame。

  • 输入视频裸数据

FILE * _yuvInputFile;

@property (strong, nonatomic) NSThread *yuvInputThread;
@property (assign, nonatomic) int yuvDataWidth;
@property (assign, nonatomic) int yuvDataHeight;

- (void)runInputYUV {
    
    int width = self.yuvDataWidth;
    int height = self.yuvDataHeight;
    int dataSize = width * height * 3 / 2;
    
    char *yuv_read_data = (char *)malloc(dataSize);
    
    while (true) {
        
        if ([self.yuvInputThread isCancelled]) {
            break;
        }
        
        size_t read = fread(yuv_read_data, dataSize, 1, _yuvInputFile);
        
        if (read > dataSize) {
            break;
        }
        if (read == 0) {
            fseek(_yuvInputFile, 0, SEEK_SET); 
            if (self.yuvInputThread) {
                continue;
            } else {
                break;
            }
        }
        
        BOOL push_error = NO;
        if (![self.yuvInputThread isExecuting]) {
            push_error = YES;
            break;
        }
        
        DingRtcVideoFrame *frame = [[DingRtcVideoFrame alloc] init];
        frame.frameType = DingRtcVideoFrameRaw;
        frame.timestamp = [[NSDate date] timeIntervalSince1970] * 1000;
        frame.width = width;
        frame.height = height;
        frame.rotation = 0;
        frame.count = 3;
        frame.data = (void *)yuv_read_data;
        frame.offset = @[@(0), @(width * height), @(width * height * 5 / 4)];
        frame.stride = @[@(width), @(width/2), @(width/2)];
        frame.pixelFormat = DingRtcVideoI420;
        int ret = [DingRtcClient.instance.rtcEngine pushExternalVideoFrame:frame track:DingRtcVideoTrackCamera];
        if (ret != 0) {
            push_error = YES;
        }
        
        //请根据实际情况控制帧率
        [NSThread sleepForTimeInterval:0.03];
        
        if (push_error) {
            break;
        }
    }
    
    free(yuv_read_data);
    fclose(_yuvInputFile);
    _yuvInputFile = NULL;
}
  • 输入视频PixcelBuffer数据

FILE * _yuvInputFile;

@property (strong, nonatomic) NSThread *yuvInputThread;
@property (assign, nonatomic) int yuvDataWidth;
@property (assign, nonatomic) int yuvDataHeight;

- (void)runInputYUV {
    
    int width = self.yuvDataWidth;
    int height = self.yuvDataHeight;
    int dataSize = width * height * 3 / 2;
    
    char *yuv_read_data = (char *)malloc(dataSize);

    CVPixelBufferPoolRef pixelBufferPool;
    NSDictionary* cvBufferProperties = @{
        (__bridge NSString*)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange),
        (__bridge NSString*)kCVPixelBufferWidthKey : @(width),
        (__bridge NSString*)kCVPixelBufferHeightKey : @(height),
    };

    CVReturn status = CVPixelBufferPoolCreate(kCFAllocatorDefault, NULL, (__bridge CFDictionaryRef)cvBufferProperties, &pixelBufferPool);
    if (status != kCVReturnSuccess || pixelBufferPool == nil) {
        return;
    }
    
    while (true) {
        
        if ([self.yuvInputThread isCancelled]) {
            break;
        }
        
        size_t read = fread(yuv_read_data, dataSize, 1, _yuvInputFile);
        
        if (read > dataSize) {
            break;
        }
        if (read == 0) {
            fseek(_yuvInputFile, 0, SEEK_SET); 
            if (self.yuvInputThread) {
                continue;
            } else {
                break;
            }
        }
        
        BOOL push_error = NO;
        if (![self.yuvInputThread isExecuting]) {
            push_error = YES;
            break;
        }
        
        CVPixelBufferRef pixelBuffer = nil;
        CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pixelBufferPool, &pixelBuffer);
        if (pixelBuffer == nil) {
            break;
        }
        CVPixelBufferLockBaseAddress(pixelBuffer, 0);
        char* pData = (char*)CVPixelBufferGetBaseAddress(pixelBuffer);
        char* pY = (char*)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
        char* pUV = (char*)CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1);
        size_t strideY = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
        size_t strideUV = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1);
        char *pSrc = yuv_read_data;
        char *pDst = pY;
        for (size_t i = 0; i < height; i++) {
            memcpy(pDst, pSrc, width);
            pSrc += width;
            pDst += strideY;
        }
        pSrc = yuv_read_data + width * height;
        pDst = pUV;
        for (size_t i = 0; i < height / 2; i++) {
            pDst = pUV + strideUV * i;
            for (size_t j = 0; j < width / 2; j++) {
                *pDst = *pSrc;
                pDst++;
                *pDst = *(pSrc+width*height/4);
                pDst++;
                pSrc++;
            }
        }
        
        DingRtcVideoFrame *frame = [[DingRtcVideoFrame alloc] init];
        frame.frameType = DingRtcVideoFramePixelBuffer;
        frame.timestamp = [[NSDate date] timeIntervalSince1970] * 1000;
        frame.width = width;
        frame.height = height;
        frame.rotation = 0;
        frame.count = 2;
        frame.data = (void*)pixelBuffer;
        frame.offset = @[@(pY - pData), @(pUV - pData)];
        frame.stride = @[@(strideY), @(strideUV)];
        frame.pixelFormat = DingRtcVideoNV12;
        
        int ret = [self.rtcClient.rtcEngine pushExternalVideoFrame:frame track:self.track];
        if (ret != 0) {
            push_error = YES;
        }
        
        //加判断频率, 默认为60hz(30毫秒)
        if (self.yuvFrequency > 0) {
            float hz = 1.0/self.yuvFrequency;
            [NSThread sleepForTimeInterval:hz];
        } else {
            [NSThread sleepForTimeInterval:0.03];
        }
        
        if (push_error) {
            break;
        }
        CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
        CVPixelBufferRelease(pixelBuffer);
        pixelBuffer = nil;
    }
    
    free(yuv_read_data);
    fclose(_yuvInputFile);
    _yuvInputFile = NULL;
}

输入外部音频流

1. 调用 setExternalAudioSource 启用外部音频输入。

//采样率
@property (assign, nonatomic) int sampleRate;

//声道数
@property (assign, nonatomic) int channels;

int ret = [DingRtcClient.instance.rtcEngine setExternalAudioSource:YES withSampleRate:self.sampleRate channelsPerFrame:self.channels];

2. 调用 pushExternalAudioFrame 输入音频数据。

FILE * _inputFile;

@property (strong, nonatomic) NSThread *inputThread;

//每个音频采样字节数, 通常是16bit(即2字节) */
@property (assign, nonatomic) int bytesPerSample;

//采样率
@property (assign, nonatomic) int sampleRate;

//声道数
@property (assign, nonatomic) int channels;

- (void)runInputPCM {
    
    int freq = 40; // audio duration (ms) each time push
    float samplesToRead = self.sampleRate / (1000.0 / freq);
    int bufferSize = samplesToRead * self.channels * self.bytesPerSample;
    
    char *buffer = (char *)malloc(bufferSize);
    
    DingRtcAudioDataSample *frame = [[DingRtcAudioDataSample alloc] init];
    frame.data = (void *)buffer;
    frame.bytesPerSample = self.bytesPerSample;
    frame.numOfChannels = self.channels;
    frame.sampleRate = self.sampleRate;
    
    while (true) {
        
        if ([self.inputThread isCancelled]) {
            break;
        }
        
        size_t readBytes = fread(buffer, 1, bufferSize, _inputFile);
    
        if (readBytes == 0) {
            fseek(_inputFile, 0, SEEK_SET);
            if (self.inputThread) {
                continue;
            } else {
                break;
            }
        }
        
        BOOL push_error = NO;
        if (![self.inputThread isExecuting]) {
            push_error = YES;
            break;
        }
        
        frame.numOfSamples = (int)(readBytes / self.bytesPerSample / selfchannels);
        int ret = [DingRtcClient.instance.rtcEngine pushExternalAudioFrame:frame];
        if (ret != 0) {
            push_error = YES;
        }
        
        //请根据实际情况控制帧率
        [NSThread sleepForTimeInterval:freq/1000.0];
        
        if (push_error) {
            break;
        }
    }
    
    free(buffer);
    fclose(_inputFile);
    _inputFile = NULL;
}