Skip to content
This repository has been archived by the owner on Mar 22, 2022. It is now read-only.

Commit

Permalink
Backport #132 HL1 frame height workaround (#137)
Browse files Browse the repository at this point in the history
Cherry-pick changes of #132

Backport workaround for the HoloLens 1 H.264 encoder bug producing
artifacts when the frame height is not a multiple of 16 pixels. These
changes allow the app to select a padding or cropping behavior to work
around the bug. On HoloLens 1, the behavior defaults to cropping.
Otherwise it defaults to no-op.

The H.264 encoder/decoder code is only available on UWP, but is compiled
on Desktop platforms, although it requires some UWP-specific libraries
to be linked against. Under normal linker stripping conditions this is
not an issue, but with the previous change the extern global was pulling
a UWP-specific module, making the link fail. This change also fix that issue
compared to the cheery-pick.
  • Loading branch information
djee-ms committed Dec 4, 2019
1 parent dfbea32 commit 8c958f9
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,33 @@ class PeerConnection : public webrtc::PeerConnectionObserver,
/// connection.
bool IsLocalVideoTrackEnabled() const noexcept;

/// Rounding mode of video frame height for |SetFrameHeightRoundMode()|.
/// This is only used on HoloLens 1 (UWP x86).
enum class FrameHeightRoundMode {
/// Leave frames unchanged.
kNone = 0,

/// Crop frame height to the nearest multiple of 16.
/// ((height - nearestLowerMultipleOf16) / 2) rows are cropped from the top
/// and (height - nearestLowerMultipleOf16 - croppedRowsTop) rows are
/// cropped from the bottom.
kCrop = 1,

/// Pad frame height to the nearest multiple of 16.
/// ((nearestHigherMultipleOf16 - height) / 2) rows are added symmetrically
/// at the top and (nearestHigherMultipleOf16 - height - addedRowsTop) rows
/// are added symmetrically at the bottom.
kPad = 2
};

/// [HoloLens 1 only]
/// Use this function to select whether resolutions where height is not
/// multiple of 16 should be cropped, padded or left unchanged. Defaults to
/// FrameHeightRoundMode::kCrop to avoid severe artifacts produced by the
/// H.264 hardware encoder. The default value is applied when creating the
/// first peer connection, so can be overridden after it.
static void SetFrameHeightRoundMode(FrameHeightRoundMode value);

//
// Audio
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1278,6 +1278,12 @@ mrsResult MRS_CALL mrsSdpForceCodecs(const char* message,
return MRS_SUCCESS;
}

void MRS_CALL
mrsSetFrameHeightRoundMode(FrameHeightRoundMode value) {
PeerConnection::SetFrameHeightRoundMode(
(PeerConnection::FrameHeightRoundMode)value);
}

void MRS_CALL mrsMemCpy(void* dst, const void* src, uint64_t size) {
memcpy(dst, src, static_cast<size_t>(size));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,13 @@ MRS_API mrsResult MRS_CALL mrsSdpForceCodecs(const char* message,
char* buffer,
uint64_t* buffer_size);

/// Must be the same as PeerConnection::FrameHeightRoundMode.
enum class FrameHeightRoundMode : int32_t { NONE = 0, CROP = 1, PAD = 2};

/// See PeerConnection::SetFrameHeightRoundMode.
MRS_API void MRS_CALL
mrsSetFrameHeightRoundMode(FrameHeightRoundMode value);

//
// Generic utilities
//
Expand Down
91 changes: 91 additions & 0 deletions libs/Microsoft.MixedReality.WebRTC.Native/src/peer_connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,93 @@

#include <functional>

#if defined(_M_IX86) /* x86 */ && defined(WINAPI_FAMILY) && \
(WINAPI_FAMILY == WINAPI_FAMILY_APP) /* UWP app */ && \
defined(_WIN32_WINNT_WIN10) && \
_WIN32_WINNT >= _WIN32_WINNT_WIN10 /* Win10 */

// Defined in
// external/webrtc-uwp-sdk/webrtc/xplatform/webrtc/third_party/winuwp_h264/H264Encoder/H264Encoder.cc
static constexpr int kFrameHeightCrop = 1;
extern int webrtc__WinUWPH264EncoderImpl__frame_height_round_mode;

#include <Windows.Foundation.h>
#include <wrl\wrappers\corewrappers.h>
#include <wrl\client.h>
#include <windows.graphics.holographic.h>

namespace {

bool CheckIfHololens() {
// The best way to check if we are running on Hololens is checking if this is
// a x86 Windows device with a transparent holographic display (AR).

using namespace Microsoft::WRL;
using namespace Microsoft::WRL::Wrappers;
using namespace ABI::Windows::Foundation;
using namespace ABI::Windows::Graphics::Holographic;

#define RETURN_IF_ERROR(...) \
if (FAILED(__VA_ARGS__)) { \
return false; \
}

RoInitializeWrapper initialize(RO_INIT_MULTITHREADED);

// HolographicSpace.IsAvailable
ComPtr<IHolographicSpaceStatics2> holo_space_statics;
RETURN_IF_ERROR(GetActivationFactory(
HStringReference(
RuntimeClass_Windows_Graphics_Holographic_HolographicSpace)
.Get(),
&holo_space_statics));
boolean is_holo_space_available;
RETURN_IF_ERROR(
holo_space_statics->get_IsAvailable(&is_holo_space_available));
if (!is_holo_space_available) {
// Not a holographic device.
return false;
}

// HolographicDisplay.GetDefault().IsOpaque
ComPtr<IHolographicDisplayStatics> holo_display_statics;
RETURN_IF_ERROR(GetActivationFactory(
HStringReference(
RuntimeClass_Windows_Graphics_Holographic_HolographicDisplay)
.Get(),
&holo_display_statics));
ComPtr<IHolographicDisplay> holo_display;
RETURN_IF_ERROR(holo_display_statics->GetDefault(&holo_display));
boolean is_opaque;
RETURN_IF_ERROR(holo_display->get_IsOpaque(&is_opaque));
// Hololens if not opaque (otherwise VR).
return !is_opaque;
#undef RETURN_IF_ERROR
}

bool IsHololens() {
static bool is_hololens = CheckIfHololens();
return is_hololens;
}
} // namespace

namespace Microsoft::MixedReality::WebRTC {
void PeerConnection::SetFrameHeightRoundMode(FrameHeightRoundMode value) {
if (IsHololens()) {
webrtc__WinUWPH264EncoderImpl__frame_height_round_mode = (int)value;
}
}
} // namespace Microsoft::MixedReality::WebRTC

#else

namespace Microsoft::MixedReality::WebRTC {
void PeerConnection::SetFrameHeightRoundMode(FrameHeightRoundMode /*value*/) {
}
} // namespace Microsoft::MixedReality::WebRTC

#endif

namespace {

/// Simple observer utility delegating to a given callback on success.
Expand Down Expand Up @@ -85,6 +172,10 @@ rtc::scoped_refptr<PeerConnection> PeerConnection::create(
webrtc::PeerConnectionFactoryInterface& factory,
const webrtc::PeerConnectionInterface::RTCConfiguration& config,
mrsPeerConnectionInteropHandle interop_handle) {
// Set the default value for the HL1 workaround before creating any
// connection. This has no effect on other platforms.
SetFrameHeightRoundMode(FrameHeightRoundMode::kCrop);

// Create the PeerConnection object
rtc::scoped_refptr<PeerConnection> peer =
new rtc::RefCountedObject<PeerConnection>(interop_handle);
Expand Down
13 changes: 10 additions & 3 deletions libs/Microsoft.MixedReality.WebRTC/Interop/InteropUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ internal struct mrsBool
/// Attribute to decorate managed delegates used as native callbacks (reverse P/Invoke).
/// Required by Mono in Ahead-Of-Time (AOT) compiling, and Unity with the IL2CPP backend.
/// </summary>
///
///
/// This attribute is required by Mono AOT and Unity IL2CPP, but not by .NET Core or Framework.
/// The implementation was copied from the Mono source code (https://github.com/mono/mono).
/// The type argument does not seem to be used anywhere in the code, and a stub implementation
Expand Down Expand Up @@ -85,13 +85,13 @@ internal struct SdpFilter

/// <summary>
/// Unsafe utility to copy a memory block with stride.
///
///
/// This utility loops over the rows of the input memory block, and copy them to the output
/// memory block, then increment the read and write pointers by the source and destination
/// strides, respectively. For each row, exactly <paramref name="elem_size"/> bytes are copied,
/// even if the row stride is higher. The extra bytes in the destination buffer past the row
/// size until the row stride are left untouched.
///
///
/// This is equivalent to the following pseudo-code:
/// <code>
/// for (int row = 0; row &lt; elem_count; ++row) {
Expand Down Expand Up @@ -153,5 +153,12 @@ public static void ThrowOnErrorCode(uint res)
throw new ArgumentOutOfRangeException("Invalid ID passed to AddDataChannelAsync().");
}
}

/// <summary>
/// See <see cref="PeerConnection.SetFrameHeightRoundMode(PeerConnection.FrameHeightRoundMode)"/>.
/// </summary>
/// <param name="value"></param>
[DllImport(dllPath, CallingConvention = CallingConvention.StdCall, EntryPoint = "mrsSetFrameHeightRoundMode")]
public static unsafe extern void SetFrameHeightRoundMode(PeerConnection.FrameHeightRoundMode value);
}
}
40 changes: 40 additions & 0 deletions libs/Microsoft.MixedReality.WebRTC/PeerConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1301,6 +1301,46 @@ public static Task<List<VideoCaptureFormat>> GetVideoCaptureFormatsAsync(string
});
}

/// <summary>
/// Frame height round mode.
/// </summary>
/// <seealso cref="SetFrameHeightRoundMode(FrameHeightRoundMode)"/>
public enum FrameHeightRoundMode
{
/// <summary>
/// Leave frames unchanged.
/// </summary>
None = 0,

/// <summary>
/// Crop frame height to the nearest multiple of 16.
/// ((height - nearestLowerMultipleOf16) / 2) rows are cropped from the top and
/// (height - nearestLowerMultipleOf16 - croppedRowsTop) rows are cropped from the bottom.
/// </summary>
Crop = 1,

/// <summary>
/// Pad frame height to the nearest multiple of 16.
/// ((nearestHigherMultipleOf16 - height) / 2) rows are added symmetrically at the top and
/// (nearestHigherMultipleOf16 - height - addedRowsTop) rows are added symmetrically at the bottom.
/// </summary>
Pad = 2
}

/// <summary>
/// [HoloLens 1 only]
/// Use this function to select whether resolutions where height is not multiple of 16
/// should be cropped, padded or left unchanged.
/// Default is <see cref="FrameHeightRoundMode.Crop"/> to avoid severe artifacts produced by
/// the H.264 hardware encoder on HoloLens 1.
/// This has no effect on other platforms.
/// </summary>
/// <param name="value">The rounding mode for video frames.</param>
public static void SetFrameHeightRoundMode(FrameHeightRoundMode value)
{
Utils.SetFrameHeightRoundMode(value);
}

internal void OnConnected()
{
IsConnected = true;
Expand Down

0 comments on commit 8c958f9

Please sign in to comment.