Skip to content

Commit c09ced2

Browse files
committed
[USBVIDEO]
- Start implement uncompressed frame capture
1 parent ed8fff7 commit c09ced2

File tree

4 files changed

+142
-32
lines changed

4 files changed

+142
-32
lines changed

drivers/usb/usbvideo/descriptor.c

Lines changed: 125 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ GUID PPINNAME_VIDEO_STILL = { STATIC_PINNAME_VIDEO_STILL };
1717
GUID gKSDATAFORMAT_TYPE_VIDEO = { STATIC_KSDATAFORMAT_TYPE_VIDEO};
1818
GUID gKSDATAFORMAT_SPECIFIER_VIDEOINFO = { STATIC_KSDATAFORMAT_SPECIFIER_VIDEOINFO};
1919
GUID gKSDATAFORMAT_SPECIFIER_VIDEOINFO2 = { STATIC_KSDATAFORMAT_SPECIFIER_VIDEOINFO2};
20-
GUID KSDATAFORMAT_SUBTYPE_MJPEG_LOCAL = {1196444237L, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71}};
20+
GUID KSDATAFORMAT_SUBTYPE_MJPEG_LOCAL = {0x47504a4d, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};
21+
GUID MEDIASUBTYPE_YUY2 = {0x32595559, 0x0000, 0x0010, {0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}};
2122

2223
KSPIN_INTERFACE StandardPinInterface =
2324
{
@@ -135,11 +136,12 @@ UsbVideoGetDataRangesForStreaming(
135136
{
136137
PUSB_VIDEO_DEVICE_EXTENSION DeviceExtension;
137138
PVS_VIDEO_INPUT_HEADER_DESCRIPTOR VideoInputHeaderDescriptor;
138-
PUSB_COMMON_DESCRIPTOR CommonDescriptor;
139+
PVC_INTERFACE_COMMON_DESCRIPTOR CommonDescriptor;
139140
PVS_MJPEG_FORMAT_TYPE_DESCRIPTOR MjpegFormatTypeDescriptor;
140141
PVS_STILL_IMAGE_FRAME_TYPE_DESCRIPTOR StillImageFormatDescriptor;
141142
PVS_UNCOMPRESSED_FORMAT_TYPE_DESCRIPTOR UncompressedFormatDescriptor;
142143
PVS_MJPEG_FRAME_TYPE_DESCRIPTOR MjpegFrameTypeDescriptor;
144+
PVS_UNCOMPRESSED_FRAME_TYPE_DESCRIPTOR UncompressedFrameTypeDescriptor;
143145
PKS_DATARANGE_VIDEO DataRangeVideo;
144146
PKS_DATARANGE_VIDEO2 DataRangeVideo2;
145147
PKSDATARANGE *DataRangeVideoArray;
@@ -149,13 +151,13 @@ UsbVideoGetDataRangesForStreaming(
149151
DeviceExtension = Device->Context;
150152
ASSERT(DeviceExtension);
151153
VideoInputHeaderDescriptor = DeviceExtension->VideoInputHeaderDescriptor;
152-
CommonDescriptor = (PUSB_COMMON_DESCRIPTOR)((ULONG_PTR)VideoInputHeaderDescriptor + VideoInputHeaderDescriptor->bLength);
154+
CommonDescriptor = (PVC_INTERFACE_COMMON_DESCRIPTOR)((ULONG_PTR)VideoInputHeaderDescriptor + VideoInputHeaderDescriptor->bLength);
153155

154156
/* enumerate all formats */
155157
FormatCount = 0;
156158
do
157159
{
158-
if (CommonDescriptor->bDescriptorType == VS_INTERFACE_HEADER_DESCRIPTOR_TYPE)
160+
if (CommonDescriptor->Common.bDescriptorType == VS_INTERFACE_HEADER_DESCRIPTOR_TYPE)
159161
{
160162
MjpegFormatTypeDescriptor = (PVS_MJPEG_FORMAT_TYPE_DESCRIPTOR)CommonDescriptor;
161163
StillImageFormatDescriptor = (PVS_STILL_IMAGE_FRAME_TYPE_DESCRIPTOR)CommonDescriptor;
@@ -172,14 +174,17 @@ UsbVideoGetDataRangesForStreaming(
172174
FormatCount += StillImageFormatDescriptor->bNumImageSizePatterns;
173175
ASSERT(FALSE);
174176
}
175-
177+
#endif
176178
else if (UncompressedFormatDescriptor->bDescriptorSubtype == VS_UNCOMPRESSED_FORMAT_TYPE_DESCRIPTOR_SUBTYPE)
177179
{
178180
DPRINT1("Found Uncompressed Format Type descriptor bNumFrameDescriptors %u\n", UncompressedFormatDescriptor->bNumFrameDescriptors);
179-
FormatCount += UncompressedFormatDescriptor->bNumFrameDescriptors;
181+
if (IsEqualGUIDAligned(&UncompressedFormatDescriptor->guidFormat,
182+
&MEDIASUBTYPE_YUY2))
183+
{
184+
FormatCount += UncompressedFormatDescriptor->bNumFrameDescriptors * 2;
185+
}
180186
}
181-
#endif
182-
CommonDescriptor = (PUSB_COMMON_DESCRIPTOR)((ULONG_PTR)CommonDescriptor + CommonDescriptor->bLength);
187+
CommonDescriptor = (PVC_INTERFACE_COMMON_DESCRIPTOR)((ULONG_PTR)CommonDescriptor + CommonDescriptor->Common.bLength);
183188
}
184189

185190
} while (((ULONG_PTR)CommonDescriptor) <((ULONG_PTR)VideoInputHeaderDescriptor + VideoInputHeaderDescriptor->wTotalLength));
@@ -199,21 +204,27 @@ UsbVideoGetDataRangesForStreaming(
199204
}
200205

201206
/* now build datarange video array */
202-
CommonDescriptor = (PUSB_COMMON_DESCRIPTOR)((ULONG_PTR)VideoInputHeaderDescriptor + VideoInputHeaderDescriptor->bLength);
207+
CommonDescriptor = (PVC_INTERFACE_COMMON_DESCRIPTOR)((ULONG_PTR)VideoInputHeaderDescriptor + VideoInputHeaderDescriptor->bLength);
203208
FormatIndex = 0;
204209
VideoFormatIndex = 0;
205210
do
206211
{
207-
if (CommonDescriptor->bDescriptorType == VS_INTERFACE_HEADER_DESCRIPTOR_TYPE)
212+
if (CommonDescriptor->Common.bDescriptorType == VS_INTERFACE_HEADER_DESCRIPTOR_TYPE)
208213
{
209-
MjpegFormatTypeDescriptor = (PVS_MJPEG_FORMAT_TYPE_DESCRIPTOR)CommonDescriptor;
210-
MjpegFrameTypeDescriptor = (PVS_MJPEG_FRAME_TYPE_DESCRIPTOR)CommonDescriptor;
211-
if (MjpegFormatTypeDescriptor->bDescriptorSubtype == VS_MJPEG_FORMAT_TYPE_DESCRIPTOR_SUBTYPE)
214+
if (CommonDescriptor->bDescriptorSubtype == VS_MJPEG_FORMAT_TYPE_DESCRIPTOR_SUBTYPE)
212215
{
216+
MjpegFormatTypeDescriptor = (PVS_MJPEG_FORMAT_TYPE_DESCRIPTOR)CommonDescriptor;
213217
VideoFormatIndex = MjpegFormatTypeDescriptor->bFormatIndex;
214218
}
215-
if (MjpegFrameTypeDescriptor->bDescriptorSubtype == VS_MJPEG_FRAME_TYPE_DESCRIPTOR_SUBTYPE)
219+
else if (CommonDescriptor->bDescriptorSubtype == VS_UNCOMPRESSED_FORMAT_TYPE_DESCRIPTOR_SUBTYPE)
220+
{
221+
UncompressedFormatDescriptor = (PVS_UNCOMPRESSED_FORMAT_TYPE_DESCRIPTOR)CommonDescriptor;
222+
VideoFormatIndex = UncompressedFormatDescriptor->bFormatIndex;
223+
}
224+
if (CommonDescriptor->bDescriptorSubtype == VS_MJPEG_FRAME_TYPE_DESCRIPTOR_SUBTYPE)
216225
{
226+
MjpegFrameTypeDescriptor = (PVS_MJPEG_FRAME_TYPE_DESCRIPTOR)CommonDescriptor;
227+
217228
DPRINT1("MjpegFrameTypeDescriptor ", MjpegFrameTypeDescriptor->bFrameIndex);
218229
DataRangeVideo = AllocFunction(sizeof(KS_DATARANGE_VIDEO));
219230
DataRangeVideo2 = AllocFunction(sizeof(KS_DATARANGE_VIDEO2));
@@ -303,12 +314,108 @@ UsbVideoGetDataRangesForStreaming(
303314
DataRangeVideoArray[FormatIndex+1] = (PKSDATARANGE)DataRangeVideo2;
304315
FormatIndex += 2;
305316
}
306-
else
317+
else if (CommonDescriptor->bDescriptorSubtype == VS_UNCOMPRESSED_FRAME_TYPE_DESCRIPTOR_SUBTYPE)
307318
{
308-
// uncompressed etc format not implemented
309-
// ignore for now
319+
if (IsEqualGUIDAligned(&UncompressedFormatDescriptor->guidFormat,
320+
&MEDIASUBTYPE_YUY2))
321+
{
322+
UncompressedFrameTypeDescriptor = (PVS_UNCOMPRESSED_FRAME_TYPE_DESCRIPTOR)CommonDescriptor;
323+
324+
DPRINT1("Uncompressed FormatType Descriptor %x", UncompressedFrameTypeDescriptor->bFrameIndex);
325+
DataRangeVideo = AllocFunction(sizeof(KS_DATARANGE_VIDEO));
326+
DataRangeVideo2 = AllocFunction(sizeof(KS_DATARANGE_VIDEO2));
327+
328+
if (DataRangeVideo == NULL || DataRangeVideo2 == NULL)
329+
{
330+
/* insufficient resources */
331+
return STATUS_INSUFFICIENT_RESOURCES;
332+
}
333+
/* init video range */
334+
DataRangeVideo->DataRange.FormatSize = sizeof(KS_DATARANGE_VIDEO);
335+
DataRangeVideo->DataRange.MajorFormat = gKSDATAFORMAT_TYPE_VIDEO;
336+
DataRangeVideo->DataRange.SampleSize = UncompressedFrameTypeDescriptor->wWidth * UncompressedFrameTypeDescriptor->wHeight * 3; // 3 bytes per pixel
337+
DataRangeVideo->DataRange.SubFormat = MEDIASUBTYPE_YUY2;
338+
DataRangeVideo->DataRange.Specifier = gKSDATAFORMAT_SPECIFIER_VIDEOINFO;
339+
DataRangeVideo->ConfigCaps.guid = gKSDATAFORMAT_SPECIFIER_VIDEOINFO;
340+
DataRangeVideo->ConfigCaps.InputSize.cx = UncompressedFrameTypeDescriptor->wWidth;
341+
DataRangeVideo->ConfigCaps.InputSize.cy = UncompressedFrameTypeDescriptor->wHeight;
342+
DataRangeVideo->ConfigCaps.MinCroppingSize.cx = UncompressedFrameTypeDescriptor->wWidth;
343+
DataRangeVideo->ConfigCaps.MinCroppingSize.cy = UncompressedFrameTypeDescriptor->wHeight;
344+
DataRangeVideo->ConfigCaps.MaxCroppingSize.cx = UncompressedFrameTypeDescriptor->wWidth;
345+
DataRangeVideo->ConfigCaps.MaxCroppingSize.cy = UncompressedFrameTypeDescriptor->wHeight;
346+
DataRangeVideo->ConfigCaps.MinOutputSize.cx = UncompressedFrameTypeDescriptor->wWidth;
347+
DataRangeVideo->ConfigCaps.MinOutputSize.cy = UncompressedFrameTypeDescriptor->wHeight;
348+
DataRangeVideo->ConfigCaps.MaxOutputSize.cx = UncompressedFrameTypeDescriptor->wWidth;
349+
DataRangeVideo->ConfigCaps.MaxOutputSize.cy = UncompressedFrameTypeDescriptor->wHeight;
350+
DataRangeVideo->ConfigCaps.OutputGranularityX = 1;
351+
DataRangeVideo->ConfigCaps.OutputGranularityY = 1;
352+
DataRangeVideo->ConfigCaps.CropGranularityX = 1;
353+
DataRangeVideo->ConfigCaps.CropGranularityY = 1;
354+
DataRangeVideo->ConfigCaps.CropAlignX = 1;
355+
DataRangeVideo->ConfigCaps.CropAlignY = 1;
356+
/* todo Alignment, MaxBitsPerSecond, MinBitsPerSecond, MinFrameInterval, MaxFrameInterval */
357+
DataRangeVideo->VideoInfoHeader.AvgTimePerFrame = 333333; // FIXME
358+
DataRangeVideo->VideoInfoHeader.dwBitRate = UncompressedFrameTypeDescriptor->wHeight * UncompressedFrameTypeDescriptor->wWidth * 3; // FIXME
359+
DataRangeVideo->VideoInfoHeader.bmiHeader.biSize = sizeof(KS_BITMAPINFOHEADER);
360+
DataRangeVideo->VideoInfoHeader.bmiHeader.biWidth = UncompressedFrameTypeDescriptor->wWidth;
361+
DataRangeVideo->VideoInfoHeader.bmiHeader.biHeight = UncompressedFrameTypeDescriptor->wHeight;
362+
DataRangeVideo->VideoInfoHeader.bmiHeader.biPlanes = 1;
363+
DataRangeVideo->VideoInfoHeader.bmiHeader.biBitCount = 16;
364+
DataRangeVideo->VideoInfoHeader.bmiHeader.biCompression = MAKEFOURCC('Y','U','Y','2');
365+
DataRangeVideo->VideoInfoHeader.bmiHeader.biSizeImage = UncompressedFrameTypeDescriptor->wWidth * UncompressedFrameTypeDescriptor->wHeight * 3;
366+
DataRangeVideo->bFixedSizeSamples = TRUE;
367+
368+
369+
DataRangeVideo2->DataRange.FormatSize = sizeof(KS_DATARANGE_VIDEO2);
370+
DataRangeVideo2->DataRange.MajorFormat = gKSDATAFORMAT_TYPE_VIDEO;
371+
DataRangeVideo2->DataRange.SampleSize = UncompressedFrameTypeDescriptor->wWidth * UncompressedFrameTypeDescriptor->wHeight * 3; // 3 bytes per pixel
372+
DataRangeVideo2->DataRange.SubFormat = MEDIASUBTYPE_YUY2;
373+
DataRangeVideo2->DataRange.Specifier = gKSDATAFORMAT_SPECIFIER_VIDEOINFO2;
374+
DataRangeVideo2->ConfigCaps.guid = gKSDATAFORMAT_SPECIFIER_VIDEOINFO2;
375+
DataRangeVideo2->ConfigCaps.InputSize.cx = UncompressedFrameTypeDescriptor->wWidth;
376+
DataRangeVideo2->ConfigCaps.InputSize.cy = UncompressedFrameTypeDescriptor->wHeight;
377+
DataRangeVideo2->ConfigCaps.MinCroppingSize.cx = UncompressedFrameTypeDescriptor->wWidth;
378+
DataRangeVideo2->ConfigCaps.MinCroppingSize.cy = UncompressedFrameTypeDescriptor->wHeight;
379+
DataRangeVideo2->ConfigCaps.MaxCroppingSize.cx = UncompressedFrameTypeDescriptor->wWidth;
380+
DataRangeVideo2->ConfigCaps.MaxCroppingSize.cy = UncompressedFrameTypeDescriptor->wHeight;
381+
DataRangeVideo2->ConfigCaps.MinOutputSize.cx = UncompressedFrameTypeDescriptor->wWidth;
382+
DataRangeVideo2->ConfigCaps.MinOutputSize.cy = UncompressedFrameTypeDescriptor->wHeight;
383+
DataRangeVideo2->ConfigCaps.MaxOutputSize.cx = UncompressedFrameTypeDescriptor->wWidth;
384+
DataRangeVideo2->ConfigCaps.MaxOutputSize.cy = UncompressedFrameTypeDescriptor->wHeight;
385+
DataRangeVideo2->ConfigCaps.OutputGranularityX = 1;
386+
DataRangeVideo2->ConfigCaps.OutputGranularityY = 1;
387+
DataRangeVideo2->ConfigCaps.CropGranularityX = 1;
388+
DataRangeVideo2->ConfigCaps.CropGranularityY = 1;
389+
DataRangeVideo2->ConfigCaps.CropAlignX = 1;
390+
DataRangeVideo2->ConfigCaps.CropAlignY = 1;
391+
/* todo Alignment, MaxBitsPerSecond, MinBitsPerSecond, MinFrameInterval, MaxFrameInterval */
392+
DataRangeVideo2->VideoInfoHeader.AvgTimePerFrame = 333333; // FIXME
393+
DataRangeVideo2->VideoInfoHeader.dwBitRate = UncompressedFrameTypeDescriptor->wHeight * UncompressedFrameTypeDescriptor->wWidth * 3; // FIXME
394+
DataRangeVideo2->VideoInfoHeader.bmiHeader.biSize = sizeof(KS_BITMAPINFOHEADER);
395+
DataRangeVideo2->VideoInfoHeader.bmiHeader.biWidth = UncompressedFrameTypeDescriptor->wWidth;
396+
DataRangeVideo2->VideoInfoHeader.bmiHeader.biHeight = UncompressedFrameTypeDescriptor->wHeight;
397+
DataRangeVideo2->VideoInfoHeader.bmiHeader.biPlanes = 1;
398+
DataRangeVideo2->VideoInfoHeader.bmiHeader.biBitCount = 16;
399+
DataRangeVideo2->VideoInfoHeader.bmiHeader.biCompression = MAKEFOURCC('Y','U','Y','2');
400+
DataRangeVideo2->VideoInfoHeader.bmiHeader.biSizeImage = UncompressedFrameTypeDescriptor->wWidth * UncompressedFrameTypeDescriptor->wHeight * 3;
401+
DataRangeVideo2->bFixedSizeSamples = TRUE;
402+
403+
404+
DeviceExtension->VideoFormatInfo[FormatIndex].bFormatIndex = VideoFormatIndex;
405+
DeviceExtension->VideoFormatInfo[FormatIndex+1].bFormatIndex = VideoFormatIndex;
406+
DeviceExtension->VideoFormatInfo[FormatIndex].bFrameIndex = UncompressedFrameTypeDescriptor->bFrameIndex;
407+
DeviceExtension->VideoFormatInfo[FormatIndex+1].bFrameIndex = UncompressedFrameTypeDescriptor->bFrameIndex;
408+
DeviceExtension->VideoFormatInfo[FormatIndex].dwFrameInterval = UncompressedFrameTypeDescriptor->dwFrameInterval[0];
409+
DeviceExtension->VideoFormatInfo[FormatIndex+1].dwFrameInterval = UncompressedFrameTypeDescriptor->dwFrameInterval[0];
410+
DeviceExtension->VideoFormatInfo[FormatIndex].Descriptor = (PUSB_COMMON_DESCRIPTOR)UncompressedFrameTypeDescriptor;
411+
DeviceExtension->VideoFormatInfo[FormatIndex+1].Descriptor = (PUSB_COMMON_DESCRIPTOR)UncompressedFrameTypeDescriptor;
412+
413+
DataRangeVideoArray[FormatIndex] = (PKSDATARANGE)DataRangeVideo;
414+
DataRangeVideoArray[FormatIndex+1] = (PKSDATARANGE)DataRangeVideo2;
415+
FormatIndex += 2;
416+
}
310417
}
311-
CommonDescriptor = (PUSB_COMMON_DESCRIPTOR)((ULONG_PTR)CommonDescriptor + CommonDescriptor->bLength);
418+
CommonDescriptor = (PVC_INTERFACE_COMMON_DESCRIPTOR)((ULONG_PTR)CommonDescriptor + CommonDescriptor->Common.bLength);
312419
}
313420

314421
} while (((ULONG_PTR)CommonDescriptor) <((ULONG_PTR)VideoInputHeaderDescriptor + VideoInputHeaderDescriptor->wTotalLength));

drivers/usb/usbvideo/pin.c

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ USBVideoPinSetDeviceState(
5858
ISO_TRANSFER_SIZE,
5959
DeviceExtension->Irp[Index],
6060
DeviceExtension->Urb[Index],
61-
&DeviceExtension->FrameCtx[Index]);
61+
&DeviceExtension->FrameCtx[0]);
6262

6363
}
6464
else
@@ -481,7 +481,7 @@ USBVideoPinCreate(
481481
DeviceExtension = Pin->Context;
482482

483483
/* init frame context */
484-
DeviceExtension->FrameCtx = AllocFunction(sizeof(FRAME_CONTEXT) * URB_POOL_COUNT);
484+
DeviceExtension->FrameCtx = AllocFunction(sizeof(FRAME_CONTEXT) * FRAME_CONTEXT_COUNT);
485485
if (!DeviceExtension->FrameCtx)
486486
{
487487
/* no memory */
@@ -547,14 +547,16 @@ USBVideoPinCreate(
547547
/* no memory */
548548
return STATUS_INSUFFICIENT_RESOURCES;
549549
}
550-
551-
DeviceExtension->FrameCtx[i].FrameBuffer = AllocFunction(720 * 1280 * 3);
552-
if (!DeviceExtension->FrameCtx[i].FrameBuffer)
550+
if (i < FRAME_CONTEXT_COUNT)
553551
{
554-
/* no memory */
555-
return STATUS_INSUFFICIENT_RESOURCES;
552+
DeviceExtension->FrameCtx[i].FrameBuffer = AllocFunction(FRAME_CONTEXT_SIZE);
553+
if (!DeviceExtension->FrameCtx[i].FrameBuffer)
554+
{
555+
/* no memory */
556+
return STATUS_INSUFFICIENT_RESOURCES;
557+
}
558+
DeviceExtension->FrameCtx[i].MaxFrameSize = FRAME_CONTEXT_SIZE;
556559
}
557-
DeviceExtension->FrameCtx[i].MaxFrameSize = 720 * 1280 * 3;
558560
}
559561
return STATUS_SUCCESS;
560562
}

drivers/usb/usbvideo/usbvideo.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,12 @@ typedef struct
6060
}STILL_PROBE_COMMIT, *PSTILL_PROBE_COMMIT;;
6161

6262

63-
#define ISO_PACKET_COUNT 16 // FIXME determine packet count by bInterval
6463
#define BULK_TRANSFER_SIZE (64*1024)
65-
#define ISO_TRANSFER_SIZE (64*1024)
64+
#define ISO_TRANSFER_SIZE (614400)
65+
#define FRAME_CONTEXT_COUNT (1)
66+
#define FRAME_CONTEXT_SIZE (640 * 480 * 2)
6667
#define URB_POOL_COUNT 4
67-
68+
#define ISO_PACKET_COUNT 16
6869

6970
typedef struct _FRAME_CONTEXT {
7071
PUCHAR FrameBuffer;

drivers/usb/usbvideo/uvc.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,9 @@ USBVideoDeliverFrame(
152152
if (BytesToCopy > 0)
153153
{
154154
DPRINT("USBVideoDeliverFrame: Copying %u bytes to user buffer (frame size %u)\n", BytesToCopy, FrameSize);
155-
Status = UvcPatchAvi1ToJfif(FrameBuffer, BytesToCopy, StreamPointer->StreamHeader->Data, &BytesToCopy);
156-
//RtlCopyMemory(StreamPointer->StreamHeader->Data, FrameBuffer, BytesToCopy);
157-
//Status = STATUS_SUCCESS;
155+
//Status = UvcPatchAvi1ToJfif(FrameBuffer, BytesToCopy, StreamPointer->StreamHeader->Data, &BytesToCopy);
156+
RtlCopyMemory(StreamPointer->StreamHeader->Data, FrameBuffer, BytesToCopy);
157+
Status = STATUS_SUCCESS;
158158
if (NT_SUCCESS(Status))
159159
{
160160
/* Advance the stream pointer to deliver the buffer */

0 commit comments

Comments
 (0)