When WebRTC is used, there are generally two ways to beautify the video: replace the acquisition module in WebRTC and beautify the video data.
1. Replace the collection module in WebRTC
It is relatively simple to replace the acquisition module in WebRTC. GPUImageVideoCamera is used to replace the video capture in WebRTC, and the image after beauty processing is obtained by GPUImage is sent to the OnFrame method of WebRTC.
Refer to the platform push and pull flow SDK: Github based on WebRTC framework
Set the beauty
- (void)setBeautyFace:(BOOL)beautyFace{
if(_beautyFace == beautyFace) return;
_beautyFace = beautyFace;
[_emptyFilter removeAllTargets];
[_filter removeAllTargets];
[_videoCamera removeAllTargets];
if(_beautyFace){
_filter = [[GPUImageBeautifyFilter alloc] init];
_emptyFilter = [[GPUImageEmptyFilter alloc] init];
}else{
_filter = [[GPUImageEmptyFilter alloc] init];
}
__weak typeof(self) _self = self;
[_filter setFrameProcessingCompletionBlock:^(GPUImageOutput *output, CMTime time) {
[_self processVideo:output];
}];
[_videoCamera addTarget:_filter];
if (beautyFace) {
[_filter addTarget:_emptyFilter];
if(_gpuImageView) [_emptyFilter addTarget:_gpuImageView];
} else {
if(_gpuImageView) [_filter addTarget:_gpuImageView]; }}Copy the code
Format conversion
The Pixel format of GPUImage after processing is BGRA. After processing, it needs to be converted to I420 format for internal processing and rendering.
WebRTC uses NV12 Pixel format when encoding, so secondary format conversion is performed when encoding
- (void) processVideo:(GPUImageOutput *)output{
rtc::CritScope cs(&cs_capture_);
if(! _isRunning) {return;
}
@autoreleasepool {
GPUImageFramebuffer *imageFramebuffer = output.framebufferForOutput;
size_t width = imageFramebuffer.size.width;
size_t height = imageFramebuffer.size.height;
uint32_t size = width * height * 3 / 2;
if(self.nWidth ! = width ||self.nHeight ! = height) {self.nWidth = width;
self.nHeight = height;
if(_dst)
delete[] _dst;
_dst = NULL;
}
if(_dst == NULL)
{
_dst = new uint8_t[size];
}
uint8_t* y_pointer = (uint8_t*)_dst;
uint8_t* u_pointer = (uint8_t*)y_pointer + width*height;
uint8_t* v_pointer = (uint8_t*)u_pointer + width*height/4;
int y_pitch = width;
int u_pitch = (width + 1) > >1;
int v_pitch = (width + 1) > >1;
libyuv::ARGBToI420([imageFramebuffer byteBuffer], width * 4, y_pointer, y_pitch, u_pointer, u_pitch, v_pointer, v_pitch, width, height);
if(self.bVideoEnable)
libyuv::I420Rect(y_pointer, y_pitch, u_pointer, u_pitch, v_pointer, v_pitch, 0.0, width, height, 32.128.128);
if(_capturer ! =nil) _capturer->CaptureYUVData(_dst, width, height, size); }}Copy the code
The data is sent to WebRTC’s OnFrame method
GPUImageVideoCapturer for GPUImage encapsulation cameras, consistent with the WebRTC collection class function, inheritance cricket: : VideoCapturer class, can the water in the WebRTC into collecting audio and video stream.
namespace webrtc {
/ / inheritance cricket: : VideoCapturer
classGPUImageVideoCapturer : public cricket::VideoCapturer { ... }}Copy the code
void GPUImageVideoCapturer::CaptureYUVData(const webrtc::VideoFrame& frame, int width, int height)
{
VideoCapturer::OnFrame(frame, width, height);
}
Copy the code
Two, the video data for beauty
The idea of beautifying video data is the traditional third-party beautifying SDK, which processes the audio and video data collected internally: The data collected internally (CVPixelBufferRef) – “convert to texture (GLuint) -” beautification of texture audio and video – “beautification of texture into iOS collection data (CVPixelBufferRef) -” is returned to WebRTC for internal rendering coding and transmission.
Thread synchronization
Internal processing is typically done using synchronous threads to ensure linear flow of data, see the code snippet in GPUImage
runSynchronouslyOnVideoProcessingQueue(^{
// Beauty treatment
});
Copy the code
Convert CVPixelBufferRef data to texture (GLuint)
Conversion mode for RGB format types
-
CoreVideo framework method: Use this method to create a CVOpenGLESTextureRef texture and get the texture ID from CVOpenGLESTextureGetName(texture).
- (GLuint)convertRGBPixelBufferToTexture:(CVPixelBufferRef)pixelBuffer { if(! pixelBuffer) {return 0; } CGSize textureSize = CGSizeMake(CVPixelBufferGetWidth(pixelBuffer), CVPixelBufferGetHeight(pixelBuffer)); CVOpenGLESTextureRef texture = nil; CVReturn status = CVOpenGLESTextureCacheCreateTextureFromImage(nil, [[GPUImageContext sharedImageProcessingContext] coreVideoTextureCache], pixelBuffer, nil, GL_TEXTURE_2D, GL_RGBA, textureSize.width, textureSize.height, GL_BGRA, GL_UNSIGNED_BYTE, 0, &texture); if(status ! = kCVReturnSuccess) {NSLog(@"Can't create texture"); } self.renderTexture = texture; return CVOpenGLESTextureGetName(texture); } Copy the code
-
OpenGL method: create a texture object, use glTexImage2D method to upload CVPixelBufferRef image data to the texture object.
glBindTexture(GL_TEXTURE_2D, [outputFramebuffer texture]); glTexImage2D(GL_TEXTURE_2D, 0, _pixelFormat==GPUPixelFormatRGB ? GL_RGB : GL_RGBA, (int)uploadedImageSize.width, (int)uploadedImageSize.height, 0, (GLint)_pixelFormat, (GLenum)_pixelType, bytesToUpload); Copy the code
YUV format type conversion mode
- (GLuint)convertYUVPixelBufferToTexture:(CVPixelBufferRef)pixelBuffer {
if (!pixelBuffer) {
return 0;
}
CGSize textureSize = CGSizeMake(CVPixelBufferGetWidth(pixelBuffer),
CVPixelBufferGetHeight(pixelBuffer));
[EAGLContext setCurrentContext:self.context];
GLuint frameBuffer;
GLuint textureID;
// FBO
glGenFramebuffers(1, &frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);
// texture
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, textureSize.width, textureSize.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureID, 0);
glViewport(0, 0, textureSize.width, textureSize.height);
// program
glUseProgram(self.yuvConversionProgram);
// texture
CVOpenGLESTextureRef luminanceTextureRef = nil;
CVOpenGLESTextureRef chrominanceTextureRef = nil;
CVReturn status = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
self.textureCache,
pixelBuffer,
nil,
GL_TEXTURE_2D,
GL_LUMINANCE,
textureSize.width,
textureSize.height,
GL_LUMINANCE,
GL_UNSIGNED_BYTE,
0,
&luminanceTextureRef);
if (status != kCVReturnSuccess) {
NSLog(@"Can't create luminanceTexture");
}
status = CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault,
self.textureCache,
pixelBuffer,
nil,
GL_TEXTURE_2D,
GL_LUMINANCE_ALPHA,
textureSize.width / 2,
textureSize.height / 2,
GL_LUMINANCE_ALPHA,
GL_UNSIGNED_BYTE,
1,
&chrominanceTextureRef);
if (status != kCVReturnSuccess) {
NSLog(@"Can't create chrominanceTexture");
}
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, CVOpenGLESTextureGetName(luminanceTextureRef));
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glUniform1i(glGetUniformLocation(self.yuvConversionProgram, "luminanceTexture"), 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, CVOpenGLESTextureGetName(chrominanceTextureRef));
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glUniform1i(glGetUniformLocation(self.yuvConversionProgram, "chrominanceTexture"), 1);
GLfloat kXDXPreViewColorConversion601FullRange[] = {
1.0, 1.0, 1.0,
0.0, -0.343, 1.765,
1.4, -0.711, 0.0,
};
GLuint yuvConversionMatrixUniform = glGetUniformLocation(self.yuvConversionProgram, "colorConversionMatrix");
glUniformMatrix3fv(yuvConversionMatrixUniform, 1, GL_FALSE, kXDXPreViewColorConversion601FullRange);
// VBO
glBindBuffer(GL_ARRAY_BUFFER, self.VBO);
GLuint positionSlot = glGetAttribLocation(self.yuvConversionProgram, "position");
glEnableVertexAttribArray(positionSlot);
glVertexAttribPointer(positionSlot, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
GLuint textureSlot = glGetAttribLocation(self.yuvConversionProgram, "inputTextureCoordinate");
glEnableVertexAttribArray(textureSlot);
glVertexAttribPointer(textureSlot, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3* sizeof(float)));
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDeleteFramebuffers(1, &frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glFlush();
self.luminanceTexture = luminanceTextureRef;
self.chrominanceTexture = chrominanceTextureRef;
if (luminanceTextureRef) {
CFRelease(luminanceTextureRef);
}
if (chrominanceTextureRef) {
CFRelease(chrominanceTextureRef);
}
return textureID;
}
Copy the code
Use GPUImageTextureInput to load filters and use GPUImageTextureOutput to output data
[GPUImageContext setActiveShaderProgram:nil];
GPUImageTextureInput *textureInput = [[ARGPUImageTextureInput alloc] initWithTexture:textureID size:size];
GPUImageSmoothToonFilter *filter = [[GPUImageSmoothToonFilter alloc] init];
[textureInput addTarget:filter];
GPUImageTextureOutput *textureOutput = [[GPUImageTextureOutput alloc] init];
[filter addTarget:textureOutput];
[textureInput processTextureWithFrameTime:kCMTimeZero];
Copy the code
You get textureOutput, which is the output texture.
The GPUImageTextureOutput texture is converted to CVPixelBufferRef data
- (CVPixelBufferRef)convertTextureToPixelBuffer:(GLuint)texture
textureSize:(CGSize)textureSize {
[EAGLContext setCurrentContext:self.context];
CVPixelBufferRef pixelBuffer = [self createPixelBufferWithSize:textureSize];
GLuint targetTextureID = [self convertRGBPixelBufferToTexture:pixelBuffer];
GLuint frameBuffer;
// FBO
glGenFramebuffers(1, &frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);
// texture
glBindTexture(GL_TEXTURE_2D, targetTextureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, textureSize.width, textureSize.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, targetTextureID, 0);
glViewport(0.0, textureSize.width, textureSize.height);
// program
glUseProgram(self.normalProgram);
// texture
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glUniform1i(glGetUniformLocation(self.normalProgram, "renderTexture"), 0);
// VBO
glBindBuffer(GL_ARRAY_BUFFER, self.VBO);
GLuint positionSlot = glGetAttribLocation(self.normalProgram, "position");
glEnableVertexAttribArray(positionSlot);
glVertexAttribPointer(positionSlot, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
GLuint textureSlot = glGetAttribLocation(self.normalProgram, "inputTextureCoordinate");
glEnableVertexAttribArray(textureSlot);
glVertexAttribPointer(textureSlot, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void(*)3* sizeof(float)));
glDrawArrays(GL_TRIANGLE_STRIP, 0.4);
glDeleteFramebuffers(1, &frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glFlush();
return pixelBuffer;
}
Copy the code
The CVPixelBufferRef after beautification is synchronized back to the SDK for rendering transmission.
Third, summary
Beauty of audio and video has become a common function of audio and video applications. In addition to the above two methods, third-party beauty can also be used. Generally, audio and video manufacturers provide self-collection function, while third-party beauty function provides beauty camera function, which can be seamlessly combined. If the requirements for beauty are not very high in its own application, it is enough to use the beauty provided by the AUDIO and video SDK (whitening, beauty, rosy). If it is used in entertainment scenes, it is necessary to integrate the third-party beauty SDK in addition to beauty, but also in beauty (thin face, big eyes), stickers (2D, 3D).