通过阅读本文,您可以了解 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;
}
文档内容是否对您有帮助?