WebRTC音视频之美颜开启后小流闪烁问题
Posted hbblzjy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WebRTC音视频之美颜开启后小流闪烁问题相关的知识,希望对你有一定的参考价值。
美颜开启后,打开双流,远端查看小流时,小流闪烁问题
项目中使用的是GPUImage进行的美颜处理,添加了美白、磨皮、亮光,但是当开启美颜效果后,如果只使用大流进行查看视频,视频美颜正常,画面正常,但是如果使用小流查看美颜画面,视频画面中会偶尔闪烁黑块,类似于大块马赛克效果,然后进行分析,如果将美颜效果关闭,小流视频画面正常,视频处理流程:将采集到的视频画面,进行YUV或者RGB美颜渲染处理,然后将画面帧传递给WebRTC进行编码,最后上行给服务器,下发给远端观看。
猜测,既然关闭美颜画面,小流画面没有问题,那么有可能是美颜处理出现的问题,于是,将美颜相关的代码进行注释,发现画面黑屏,但是依然有黑块闪动,所以大概率不是美颜处理造成的问题。
继续分析代码,发现先美颜,后编码的过程中,都是处理的同一个地址的视频帧数据,但是美颜和编码是在两个不同的异步线程进行处理,这就会导致,编码处理的数据和美颜处理的数据不一致,因为美颜会改变YUV数据,所以有可能小流时造成黑块闪动问题。于是添加了一个copy视频数据的方法,使编码处理的数据和美颜处理的数据保持一致,然后检测发现小流不再闪动,问题解决了(无比开心!无比激动!)。
copy视频数据地址的方法
/// RGB/BGR buffer copy
+ (CVPixelBufferRef)RGBBuffereCopyWithPixelBuffer:(CVPixelBufferRef)pixelBuffer {
// Get pixel buffer info
CVPixelBufferLockBaseAddress(pixelBuffer, 0);
int bufferWidth = (int)CVPixelBufferGetWidth(pixelBuffer);
int bufferHeight = (int)CVPixelBufferGetHeight(pixelBuffer);
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer);
uint8_t *baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer);
OSType pixelFormat = kCVPixelFormatType_32BGRA;
// Copy the pixel buffer
CVPixelBufferRef pixelBufferCopy = NULL;
CFDictionaryRef empty = CFDictionaryCreate(kCFAllocatorDefault, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); // our empty iosurface properties dictionary
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], kCVPixelBufferCGImageCompatibilityKey,
[NSNumber numberWithBool:YES], kCVPixelBufferCGBitmapContextCompatibilityKey,
empty, kCVPixelBufferIOSurfacePropertiesKey,
nil];
CVReturn status = CVPixelBufferCreate(kCFAllocatorDefault, bufferWidth, bufferHeight, pixelFormat, (__bridge CFDictionaryRef) options, &pixelBufferCopy);
if (status == kCVReturnSuccess) {
CVPixelBufferLockBaseAddress(pixelBufferCopy, 0);
uint8_t *copyBaseAddress = CVPixelBufferGetBaseAddress(pixelBufferCopy);
memcpy(copyBaseAddress, baseAddress, bufferHeight * bytesPerRow);
CVPixelBufferUnlockBaseAddress(pixelBufferCopy, 0);
}else {
SmoothLogE("RGBBuffereCopyWithPixelBuffer :: failed");
}
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
return pixelBufferCopy;
}
然而高兴的太早了,测试时发现,稳定性变差了,原因是copy了一份数据,运行时间稍微长点儿,内存就会增长,在旧iPhone上体验不好,于是继续深入研究......
根据闪动黑块的现象和注释美颜代码后的画布现象,感觉像是编码的画面和美颜的画面不是同一个尺寸大小,于是分析编码代码,然后发现有一个同播编码适配器的方法(int SimulcastEncoderAdapter::Encode)进行编码,方法中有对视频画面进行缩放处理,于是尝试将此方法进行更改。
int SimulcastEncoderAdapter::Encode(
const VideoFrame& input_image,
const std::vector<VideoFrameType>* frame_types) {
RTC_DCHECK_RUN_ON(&encoder_queue_);
if (!Initialized()) {
return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
}
if (encoded_complete_callback_ == nullptr) {
return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
}
// All active streams should generate a key frame if
// a key frame is requested by any stream.
bool send_key_frame = false;
if (frame_types) {
for (size_t i = 0; i < frame_types->size(); ++i) {
if (frame_types->at(i) == VideoFrameType::kVideoFrameKey) {
send_key_frame = true;
break;
}
}
}
for (size_t stream_idx = 0; stream_idx < streaminfos_.size(); ++stream_idx) {
if (streaminfos_[stream_idx].key_frame_request &&
streaminfos_[stream_idx].send_stream) {
send_key_frame = true;
break;
}
}
int src_width = input_image.width();
int src_height = input_image.height();
for (size_t stream_idx = 0; stream_idx < streaminfos_.size(); ++stream_idx) {
// Don't encode frames in resolutions that we don't intend to send.
if (!streaminfos_[stream_idx].send_stream) {
continue;
}
std::vector<VideoFrameType> stream_frame_types;
if (send_key_frame) {
stream_frame_types.push_back(VideoFrameType::kVideoFrameKey);
streaminfos_[stream_idx].key_frame_request = false;
} else {
stream_frame_types.push_back(VideoFrameType::kVideoFrameDelta);
}
int dst_width = streaminfos_[stream_idx].width;
int dst_height = streaminfos_[stream_idx].height;
// If scaling isn't required, because the input resolution
// matches the destination or the input image is empty (e.g.
// a keyframe request for encoders with internal camera
// sources) or the source image has a native handle, pass the image on
// directly. Otherwise, we'll scale it to match what the encoder expects
// (below).
// For texture frames, the underlying encoder is expected to be able to
// correctly sample/scale the source texture.
// TODO(perkj): ensure that works going forward, and figure out how this
// affects webrtc:5683.
/**
如果不需要缩放,因为输入分辨率与目标匹配,或者输入图像为空(例如,带有内部相机源的编码器的关键帧请求),或者源图像具有本机句柄,则直接传递图像。否则,我们将对其进行缩放以匹配编码器期望的内容(如下所示)。对于纹理帧,底层编码器应能够正确采样/缩放源纹理。TODO(perkj):确保这项工作继续下去,并找出这对webrtc:5683的影响。
不进行缩放,保持大小流两个数据一致,进行编码,同时注释掉RTCVideoEncoderH264.mm编码方法- (NSInteger)encode:(RTCVideoFrame *)frame
codecSpecificInfo:(nullable id<RTCCodecSpecificInfo>)codecSpecificInfo
frameTypes:(NSArray<NSNumber *> *)frameTypes中宽、高判断:
// RTC_DCHECK_EQ(frame.width, _width);
// RTC_DCHECK_EQ(frame.height, _height);
可以解决小流闪动的问题
*/
// if ((dst_width == src_width && dst_height == src_height) /*||
// input_image.video_frame_buffer()->type() ==
// VideoFrameBuffer::Type::kNative*/) {
int ret = streaminfos_[stream_idx].encoder->Encode(input_image,
&stream_frame_types);
if (ret != WEBRTC_VIDEO_CODEC_OK) {
return ret;
}
// } else {
// rtc::scoped_refptr<I420Buffer> dst_buffer =
// I420Buffer::Create(dst_width, dst_height);
// rtc::scoped_refptr<I420BufferInterface> src_buffer =
// input_image.video_frame_buffer()->ToI420();
// libyuv::I420Scale(src_buffer->DataY(), src_buffer->StrideY(),
// src_buffer->DataU(), src_buffer->StrideU(),
// src_buffer->DataV(), src_buffer->StrideV(), src_width,
// src_height, dst_buffer->MutableDataY(),
// dst_buffer->StrideY(), dst_buffer->MutableDataU(),
// dst_buffer->StrideU(), dst_buffer->MutableDataV(),
// dst_buffer->StrideV(), dst_width, dst_height,
// libyuv::kFilterBilinear);
//
// // UpdateRect is not propagated to lower simulcast layers currently.
// // TODO(ilnik): Consider scaling UpdateRect together with the buffer.
// VideoFrame frame(input_image);
// frame.set_video_frame_buffer(dst_buffer);
// frame.set_rotation(webrtc::kVideoRotation_0);
// frame.set_update_rect(
// VideoFrame::UpdateRect{0, 0, frame.width(), frame.height()});
// int ret =
// streaminfos_[stream_idx].encoder->Encode(frame, &stream_frame_types);
// if (ret != WEBRTC_VIDEO_CODEC_OK) {
// return ret;
// }
// }
}
return WEBRTC_VIDEO_CODEC_OK;
}
然后效果测试,发现小流闪动问题解决,并且内存使用情况基本和以前保持一致,于是可以说彻底解决了此问题。(无比开心,终于解决了一个疑难杂症,坚持就是胜利!!!)
以上是关于WebRTC音视频之美颜开启后小流闪烁问题的主要内容,如果未能解决你的问题,请参考以下文章