Skip to content
This repository was archived by the owner on Oct 25, 2024. It is now read-only.

Commit 76d3e7a

Browse files
eshrubsCommit Bot
authored andcommitted
NV12 support for VP8 simulcast
Tested using video_loopback with generated NV12 frames. Bug: webrtc:11635, webrtc:11975 Change-Id: I14b2d663c55a83d80e48e226fcf706cb18903193 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/186722 Commit-Queue: Evan Shrubsole <[email protected]> Reviewed-by: Ilya Nikolaevskiy <[email protected]> Cr-Commit-Position: refs/heads/master@{#32325}
1 parent b298f74 commit 76d3e7a

File tree

3 files changed

+158
-42
lines changed

3 files changed

+158
-42
lines changed

modules/video_coding/codecs/vp8/libvpx_vp8_encoder.cc

Lines changed: 114 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,10 @@ int LibvpxVp8Encoder::InitEncode(const VideoCodec* inst,
497497
return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
498498
}
499499

500+
// Use the previous pixel format to avoid extra image allocations.
501+
vpx_img_fmt_t pixel_format =
502+
raw_images_.empty() ? VPX_IMG_FMT_I420 : raw_images_[0].fmt;
503+
500504
int retVal = Release();
501505
if (retVal < 0) {
502506
return retVal;
@@ -650,8 +654,8 @@ int LibvpxVp8Encoder::InitEncode(const VideoCodec* inst,
650654
// Creating a wrapper to the image - setting image data to NULL.
651655
// Actual pointer will be set in encode. Setting align to 1, as it
652656
// is meaningless (no memory allocation is done here).
653-
libvpx_->img_wrap(&raw_images_[0], VPX_IMG_FMT_I420, inst->width,
654-
inst->height, 1, NULL);
657+
libvpx_->img_wrap(&raw_images_[0], pixel_format, inst->width, inst->height, 1,
658+
NULL);
655659

656660
// Note the order we use is different from webm, we have lowest resolution
657661
// at position 0 and they have highest resolution at position 0.
@@ -699,10 +703,9 @@ int LibvpxVp8Encoder::InitEncode(const VideoCodec* inst,
699703
// Setting alignment to 32 - as that ensures at least 16 for all
700704
// planes (32 for Y, 16 for U,V). Libvpx sets the requested stride for
701705
// the y plane, but only half of it to the u and v planes.
702-
libvpx_->img_alloc(&raw_images_[i], VPX_IMG_FMT_I420,
703-
inst->simulcastStream[stream_idx].width,
704-
inst->simulcastStream[stream_idx].height,
705-
kVp832ByteAlign);
706+
libvpx_->img_alloc(
707+
&raw_images_[i], pixel_format, inst->simulcastStream[stream_idx].width,
708+
inst->simulcastStream[stream_idx].height, kVp832ByteAlign);
706709
SetStreamState(stream_bitrates[stream_idx] > 0, stream_idx);
707710
vpx_configs_[i].rc_target_bitrate = stream_bitrates[stream_idx];
708711
if (stream_bitrates[stream_idx] > 0) {
@@ -1014,26 +1017,12 @@ int LibvpxVp8Encoder::Encode(const VideoFrame& frame,
10141017
flags[i] = send_key_frame ? VPX_EFLAG_FORCE_KF : EncodeFlags(tl_configs[i]);
10151018
}
10161019

1017-
rtc::scoped_refptr<I420BufferInterface> input_image =
1018-
frame.video_frame_buffer()->ToI420();
1019-
// Since we are extracting raw pointers from |input_image| to
1020-
// |raw_images_[0]|, the resolution of these frames must match.
1021-
RTC_DCHECK_EQ(input_image->width(), raw_images_[0].d_w);
1022-
RTC_DCHECK_EQ(input_image->height(), raw_images_[0].d_h);
1023-
1024-
// Image in vpx_image_t format.
1025-
// Input image is const. VP8's raw image is not defined as const.
1026-
raw_images_[0].planes[VPX_PLANE_Y] =
1027-
const_cast<uint8_t*>(input_image->DataY());
1028-
raw_images_[0].planes[VPX_PLANE_U] =
1029-
const_cast<uint8_t*>(input_image->DataU());
1030-
raw_images_[0].planes[VPX_PLANE_V] =
1031-
const_cast<uint8_t*>(input_image->DataV());
1032-
1033-
raw_images_[0].stride[VPX_PLANE_Y] = input_image->StrideY();
1034-
raw_images_[0].stride[VPX_PLANE_U] = input_image->StrideU();
1035-
raw_images_[0].stride[VPX_PLANE_V] = input_image->StrideV();
1036-
1020+
rtc::scoped_refptr<VideoFrameBuffer> input_image = frame.video_frame_buffer();
1021+
if (input_image->type() != VideoFrameBuffer::Type::kI420 &&
1022+
input_image->type() != VideoFrameBuffer::Type::kNV12) {
1023+
input_image = input_image->ToI420();
1024+
}
1025+
PrepareRawImagesForEncoding(input_image);
10371026
struct CleanUpOnExit {
10381027
explicit CleanUpOnExit(vpx_image_t& raw_image) : raw_image_(raw_image) {}
10391028
~CleanUpOnExit() {
@@ -1044,22 +1033,6 @@ int LibvpxVp8Encoder::Encode(const VideoFrame& frame,
10441033
vpx_image_t& raw_image_;
10451034
} clean_up_on_exit(raw_images_[0]);
10461035

1047-
for (size_t i = 1; i < encoders_.size(); ++i) {
1048-
// Scale the image down a number of times by downsampling factor
1049-
libyuv::I420Scale(
1050-
raw_images_[i - 1].planes[VPX_PLANE_Y],
1051-
raw_images_[i - 1].stride[VPX_PLANE_Y],
1052-
raw_images_[i - 1].planes[VPX_PLANE_U],
1053-
raw_images_[i - 1].stride[VPX_PLANE_U],
1054-
raw_images_[i - 1].planes[VPX_PLANE_V],
1055-
raw_images_[i - 1].stride[VPX_PLANE_V], raw_images_[i - 1].d_w,
1056-
raw_images_[i - 1].d_h, raw_images_[i].planes[VPX_PLANE_Y],
1057-
raw_images_[i].stride[VPX_PLANE_Y], raw_images_[i].planes[VPX_PLANE_U],
1058-
raw_images_[i].stride[VPX_PLANE_U], raw_images_[i].planes[VPX_PLANE_V],
1059-
raw_images_[i].stride[VPX_PLANE_V], raw_images_[i].d_w,
1060-
raw_images_[i].d_h, libyuv::kFilterBilinear);
1061-
}
1062-
10631036
if (send_key_frame) {
10641037
// Adapt the size of the key frame when in screenshare with 1 temporal
10651038
// layer.
@@ -1309,6 +1282,105 @@ int LibvpxVp8Encoder::RegisterEncodeCompleteCallback(
13091282
return WEBRTC_VIDEO_CODEC_OK;
13101283
}
13111284

1285+
void LibvpxVp8Encoder::PrepareRawImagesForEncoding(
1286+
const rtc::scoped_refptr<VideoFrameBuffer>& frame) {
1287+
// Since we are extracting raw pointers from |input_image| to
1288+
// |raw_images_[0]|, the resolution of these frames must match.
1289+
RTC_DCHECK_EQ(frame->width(), raw_images_[0].d_w);
1290+
RTC_DCHECK_EQ(frame->height(), raw_images_[0].d_h);
1291+
switch (frame->type()) {
1292+
case VideoFrameBuffer::Type::kI420:
1293+
return PrepareI420Image(frame->GetI420());
1294+
case VideoFrameBuffer::Type::kNV12:
1295+
return PrepareNV12Image(frame->GetNV12());
1296+
default:
1297+
RTC_NOTREACHED();
1298+
}
1299+
}
1300+
1301+
void LibvpxVp8Encoder::MaybeUpdatePixelFormat(vpx_img_fmt fmt) {
1302+
RTC_DCHECK(!raw_images_.empty());
1303+
if (raw_images_[0].fmt == fmt) {
1304+
RTC_DCHECK(std::all_of(
1305+
std::next(raw_images_.begin()), raw_images_.end(),
1306+
[fmt](const vpx_image_t& raw_img) { return raw_img.fmt == fmt; }))
1307+
<< "Not all raw images had the right format!";
1308+
return;
1309+
}
1310+
RTC_LOG(INFO) << "Updating vp8 encoder pixel format to "
1311+
<< (fmt == VPX_IMG_FMT_NV12 ? "NV12" : "I420");
1312+
for (size_t i = 0; i < raw_images_.size(); ++i) {
1313+
vpx_image_t& img = raw_images_[i];
1314+
auto d_w = img.d_w;
1315+
auto d_h = img.d_h;
1316+
libvpx_->img_free(&img);
1317+
// First image is wrapping the input frame, the rest are allocated.
1318+
if (i == 0) {
1319+
libvpx_->img_wrap(&img, fmt, d_w, d_h, 1, NULL);
1320+
} else {
1321+
libvpx_->img_alloc(&img, fmt, d_w, d_h, kVp832ByteAlign);
1322+
}
1323+
}
1324+
}
1325+
1326+
void LibvpxVp8Encoder::PrepareI420Image(const I420BufferInterface* frame) {
1327+
RTC_DCHECK(!raw_images_.empty());
1328+
MaybeUpdatePixelFormat(VPX_IMG_FMT_I420);
1329+
// Image in vpx_image_t format.
1330+
// Input image is const. VP8's raw image is not defined as const.
1331+
raw_images_[0].planes[VPX_PLANE_Y] = const_cast<uint8_t*>(frame->DataY());
1332+
raw_images_[0].planes[VPX_PLANE_U] = const_cast<uint8_t*>(frame->DataU());
1333+
raw_images_[0].planes[VPX_PLANE_V] = const_cast<uint8_t*>(frame->DataV());
1334+
1335+
raw_images_[0].stride[VPX_PLANE_Y] = frame->StrideY();
1336+
raw_images_[0].stride[VPX_PLANE_U] = frame->StrideU();
1337+
raw_images_[0].stride[VPX_PLANE_V] = frame->StrideV();
1338+
1339+
for (size_t i = 1; i < encoders_.size(); ++i) {
1340+
// Scale the image down a number of times by downsampling factor
1341+
libyuv::I420Scale(
1342+
raw_images_[i - 1].planes[VPX_PLANE_Y],
1343+
raw_images_[i - 1].stride[VPX_PLANE_Y],
1344+
raw_images_[i - 1].planes[VPX_PLANE_U],
1345+
raw_images_[i - 1].stride[VPX_PLANE_U],
1346+
raw_images_[i - 1].planes[VPX_PLANE_V],
1347+
raw_images_[i - 1].stride[VPX_PLANE_V], raw_images_[i - 1].d_w,
1348+
raw_images_[i - 1].d_h, raw_images_[i].planes[VPX_PLANE_Y],
1349+
raw_images_[i].stride[VPX_PLANE_Y], raw_images_[i].planes[VPX_PLANE_U],
1350+
raw_images_[i].stride[VPX_PLANE_U], raw_images_[i].planes[VPX_PLANE_V],
1351+
raw_images_[i].stride[VPX_PLANE_V], raw_images_[i].d_w,
1352+
raw_images_[i].d_h, libyuv::kFilterBilinear);
1353+
}
1354+
}
1355+
1356+
void LibvpxVp8Encoder::PrepareNV12Image(const NV12BufferInterface* frame) {
1357+
RTC_DCHECK(!raw_images_.empty());
1358+
MaybeUpdatePixelFormat(VPX_IMG_FMT_NV12);
1359+
// Image in vpx_image_t format.
1360+
// Input image is const. VP8's raw image is not defined as const.
1361+
raw_images_[0].planes[VPX_PLANE_Y] = const_cast<uint8_t*>(frame->DataY());
1362+
raw_images_[0].planes[VPX_PLANE_U] = const_cast<uint8_t*>(frame->DataUV());
1363+
raw_images_[0].planes[VPX_PLANE_V] = raw_images_[0].planes[VPX_PLANE_U] + 1;
1364+
raw_images_[0].stride[VPX_PLANE_Y] = frame->StrideY();
1365+
raw_images_[0].stride[VPX_PLANE_U] = frame->StrideUV();
1366+
raw_images_[0].stride[VPX_PLANE_V] = frame->StrideUV();
1367+
1368+
for (size_t i = 1; i < encoders_.size(); ++i) {
1369+
// Scale the image down a number of times by downsampling factor
1370+
libyuv::NV12Scale(
1371+
raw_images_[i - 1].planes[VPX_PLANE_Y],
1372+
raw_images_[i - 1].stride[VPX_PLANE_Y],
1373+
raw_images_[i - 1].planes[VPX_PLANE_U],
1374+
raw_images_[i - 1].stride[VPX_PLANE_U], raw_images_[i - 1].d_w,
1375+
raw_images_[i - 1].d_h, raw_images_[i].planes[VPX_PLANE_Y],
1376+
raw_images_[i].stride[VPX_PLANE_Y], raw_images_[i].planes[VPX_PLANE_U],
1377+
raw_images_[i].stride[VPX_PLANE_U], raw_images_[i].d_w,
1378+
raw_images_[i].d_h, libyuv::kFilterBilinear);
1379+
raw_images_[i].planes[VPX_PLANE_V] = raw_images_[i].planes[VPX_PLANE_U] + 1;
1380+
raw_images_[i].stride[VPX_PLANE_V] = raw_images_[i].stride[VPX_PLANE_U] + 1;
1381+
}
1382+
}
1383+
13121384
// static
13131385
LibvpxVp8Encoder::VariableFramerateExperiment
13141386
LibvpxVp8Encoder::ParseVariableFramerateConfig(std::string group_name) {

modules/video_coding/codecs/vp8/libvpx_vp8_encoder.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ class LibvpxVp8Encoder : public VideoEncoder {
9393

9494
bool UpdateVpxConfiguration(size_t stream_index);
9595

96+
void PrepareRawImagesForEncoding(
97+
const rtc::scoped_refptr<VideoFrameBuffer>& frame);
98+
void MaybeUpdatePixelFormat(vpx_img_fmt fmt);
99+
void PrepareI420Image(const I420BufferInterface* frame);
100+
void PrepareNV12Image(const NV12BufferInterface* frame);
101+
96102
const std::unique_ptr<LibvpxInterface> libvpx_;
97103

98104
const CpuSpeedExperiment experimental_cpu_speed_config_arm_;

modules/video_coding/codecs/vp8/test/vp8_impl_unittest.cc

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,44 @@ TEST_F(TestVp8Impl, EncodeFrameAndRelease) {
266266
encoder_->Encode(NextInputFrame(), nullptr));
267267
}
268268

269+
TEST_F(TestVp8Impl, EncodeNv12FrameSimulcast) {
270+
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, encoder_->Release());
271+
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
272+
encoder_->InitEncode(&codec_settings_, kSettings));
273+
274+
EncodedImage encoded_frame;
275+
CodecSpecificInfo codec_specific_info;
276+
input_frame_generator_ = test::CreateSquareFrameGenerator(
277+
kWidth, kHeight, test::FrameGeneratorInterface::OutputType::kNV12,
278+
absl::nullopt);
279+
EncodeAndWaitForFrame(NextInputFrame(), &encoded_frame, &codec_specific_info);
280+
281+
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, encoder_->Release());
282+
EXPECT_EQ(WEBRTC_VIDEO_CODEC_UNINITIALIZED,
283+
encoder_->Encode(NextInputFrame(), nullptr));
284+
}
285+
286+
TEST_F(TestVp8Impl, EncodeI420FrameAfterNv12Frame) {
287+
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, encoder_->Release());
288+
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,
289+
encoder_->InitEncode(&codec_settings_, kSettings));
290+
291+
EncodedImage encoded_frame;
292+
CodecSpecificInfo codec_specific_info;
293+
input_frame_generator_ = test::CreateSquareFrameGenerator(
294+
kWidth, kHeight, test::FrameGeneratorInterface::OutputType::kNV12,
295+
absl::nullopt);
296+
EncodeAndWaitForFrame(NextInputFrame(), &encoded_frame, &codec_specific_info);
297+
input_frame_generator_ = test::CreateSquareFrameGenerator(
298+
kWidth, kHeight, test::FrameGeneratorInterface::OutputType::kI420,
299+
absl::nullopt);
300+
EncodeAndWaitForFrame(NextInputFrame(), &encoded_frame, &codec_specific_info);
301+
302+
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, encoder_->Release());
303+
EXPECT_EQ(WEBRTC_VIDEO_CODEC_UNINITIALIZED,
304+
encoder_->Encode(NextInputFrame(), nullptr));
305+
}
306+
269307
TEST_F(TestVp8Impl, InitDecode) {
270308
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK, decoder_->Release());
271309
EXPECT_EQ(WEBRTC_VIDEO_CODEC_OK,

0 commit comments

Comments
 (0)