FreeRDP
Loading...
Searching...
No Matches
TestCameraAVFoundationNV12.c
1
18#include <stdio.h>
19#include <string.h>
20#include <winpr/assert.h>
21#include <freerdp/codec/color.h>
22#include "../nv12.h"
23#include "../../camera.h"
24#include "../../../common/rdpecam-utils.h"
25
26/* Exercise the same shared encoder used for NV12 capture when H.264 output is offered. */
27WINPR_ATTR_NODISCARD
28static BOOL test_h264_encoding(void)
29{
30 if (!freerdp_video_conversion_supported(FREERDP_VIDEO_FORMAT_NV12, FREERDP_VIDEO_FORMAT_H264))
31 return TRUE;
32
33 enum
34 {
35 width = 64,
36 height = 48,
37 stride = width * 4
38 };
39 BOOL result = FALSE;
40 CameraDeviceStream stream = { 0 };
41 wStream* sample = Stream_New(NULL, 1);
42 H264_CONTEXT* decoder = h264_context_new(FALSE);
43 BYTE raw[width * height * 3 / 2] = { 0 };
44 BYTE rgb[stride * height] = { 0 };
45 const RECTANGLE_16 region = { 0, 0, width, height };
46 stream.formats.inputFormat = CAM_MEDIA_FORMAT_NV12;
47 stream.formats.outputFormat = CAM_MEDIA_FORMAT_H264;
48 stream.currMediaType.Width = width;
49 stream.currMediaType.Height = height;
50 stream.currMediaType.FrameRateNumerator = 30;
51 stream.currMediaType.FrameRateDenominator = 1;
52 if (!sample || !decoder || !ecam_encoder_context_init(&stream) ||
53 !h264_context_reset(decoder, width, height))
54 goto out;
55
56 memset(raw + width * height, 128, sizeof(raw) - width * height);
57 for (size_t frame = 0; frame < 3; frame++)
58 {
59 memset(raw, (int)(96 + frame * 16), width * height);
60 memset(rgb, 0xa5, sizeof(rgb));
61 Stream_ResetPosition(sample);
62 Stream_Write_UINT8(sample, 0x5a);
63 if (!ecam_encoder_compress(&stream, raw, sizeof(raw), sample) ||
64 (Stream_GetPosition(sample) <= 1) || (Stream_Buffer(sample)[0] != 0x5a))
65 goto out;
66 const size_t size = Stream_GetPosition(sample) - 1;
67 if ((size > UINT32_MAX) ||
68 (avc420_decompress(decoder, Stream_Buffer(sample) + 1, (UINT32)size, rgb,
69 PIXEL_FORMAT_BGRA32, stride, width, height, &region, 1) < 0))
70 goto out;
71 /* The decoded neutral-gray frames must change with their source luminance. */
72 const BYTE* pixel = rgb + (height / 2) * stride + (width / 2) * 4;
73 const int expected = (int)((80 + frame * 16) * 255 / 219);
74 for (size_t channel = 0; channel < 3; channel++)
75 {
76 const int difference = (int)pixel[channel] - expected;
77 if ((difference < -8) || (difference > 8))
78 goto out;
79 }
80 }
81 result = TRUE;
82out:
83 if (!ecam_encoder_context_free(&stream))
84 result = FALSE;
85 h264_context_free(decoder);
86 Stream_Free(sample, TRUE);
87 if (!result)
88 fprintf(stderr, "NV12 camera H.264 encoding or decoding failed\n");
89 return result;
90}
91
92WINPR_ATTR_NODISCARD
93int TestCameraAVFoundationNV12(int argc, char* argv[])
94{
95 WINPR_UNUSED(argc);
96 WINPR_ASSERT(argv);
97 WINPR_UNUSED(argv);
98 CVPixelBufferRef image = NULL;
99 CameraDeviceStream stream = { 0 };
100 wStream* sample = Stream_New(NULL, 1);
101 int result = -1;
102 if (!sample || CVPixelBufferCreate(NULL, 8, 4, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange,
103 NULL, &image) != kCVReturnSuccess)
104 goto out;
105 if (CVPixelBufferLockBaseAddress(image, 0) != kCVReturnSuccess)
106 goto out;
107 BYTE expected[48] = { 0 };
108 size_t offset = 0;
109 for (size_t plane = 0; plane < 2; plane++)
110 {
111 BYTE* data = CVPixelBufferGetBaseAddressOfPlane(image, plane);
112 const size_t stride = CVPixelBufferGetBytesPerRowOfPlane(image, plane);
113 for (size_t row = 0; row < CVPixelBufferGetHeightOfPlane(image, plane); row++)
114 {
115 /* Distinct rows surrounded by padding that must not reach the wire. */
116 memset(data + row * stride, 0xff, stride);
117 memset(data + row * stride, (int)(plane * 16 + row), 8);
118 memset(expected + offset, (int)(plane * 16 + row), 8);
119 offset += 8;
120 }
121 }
122 CVPixelBufferUnlockBaseAddress(image, 0);
123 if (!cam_avfoundation_pixel_buffer_to_nv12(image, sample) ||
124 (Stream_Length(sample) != sizeof(expected)) ||
125 memcmp(Stream_Buffer(sample), expected, sizeof(expected)) != 0)
126 goto out;
127 /* Transport the packed bytes with no software encoder, including in a
128 * VideoToolbox decoder-only build. Preserve any existing packet header. */
129 stream.formats.inputFormat = CAM_MEDIA_FORMAT_NV12;
130 stream.formats.outputFormat = CAM_MEDIA_FORMAT_NV12;
131 stream.currMediaType.Width = 8;
132 stream.currMediaType.Height = 4;
133 stream.currMediaType.FrameRateNumerator = 30;
134 stream.currMediaType.FrameRateDenominator = 1;
135 Stream_ResetPosition(sample);
136 Stream_Write_UINT8(sample, 0x5a);
137 if (!ecam_encoder_context_init(&stream) ||
138 !ecam_encoder_compress(&stream, expected, sizeof(expected), sample) ||
139 (Stream_GetPosition(sample) != sizeof(expected) + 1) ||
140 (Stream_Buffer(sample)[0] != 0x5a) ||
141 memcmp(Stream_Buffer(sample) + 1, expected, sizeof(expected)) != 0)
142 goto out;
143 /* MS-RDPECAM 2.2.3.8.1 permits 0, 1, 2 and 3; all other bytes contain unknown flags. */
144 wLog* flagsLog = WLog_Get("camera.test.flags");
145 if (!flagsLog || !WLog_SetLogLevel(flagsLog, WLOG_OFF))
146 goto out;
147 for (unsigned flags = 0; flags <= UINT8_MAX; flags++)
148 {
149 const BOOL valid = rdpecam_valid_MediaTypeDescriptionFlags_((UINT8)flags, flagsLog,
150 __FILE__, __func__, __LINE__);
151 if (valid != (flags <= 3))
152 {
153 fprintf(stderr, "Incorrect media type flag validation for 0x%02x\n", flags);
154 goto out;
155 }
156 }
157 CVPixelBufferRef invalid = NULL;
158 if (CVPixelBufferCreate(NULL, 8, 4, kCVPixelFormatType_32ARGB, NULL, &invalid) !=
159 kCVReturnSuccess)
160 goto out;
161 const BOOL accepted = cam_avfoundation_pixel_buffer_to_nv12(invalid, sample);
162 CFRelease(invalid);
163 if (accepted)
164 goto out;
165 if (!test_h264_encoding())
166 goto out;
167 result = 0;
168out:
169 if (!ecam_encoder_context_free(&stream))
170 result = -1;
171 if (image)
172 CFRelease(image);
173 Stream_Free(sample, TRUE);
174 if (result != 0)
175 fprintf(stderr, "NV12 camera sample packing or flags validation failed\n");
176 return result;
177}