public void initialize(iVideoTrack videoTrack, int encodedBuffersCount) { // dbgPrintEncodedFormats(); // Determine format of the encoded video sPixelFormatMP encodedFormat = videoTrack.getEncodedFormat(); // Set format of the first and the only plane of the compressed video. encodedFormat.numPlanes = 1; encodedFormat.setPlaneFormat(0, new sPlanePixelFormat() { sizeImage = EncodedQueue.encodedVideoBufferSize(videoTrack) }); // 4.5.1.5. Initialization // 1. Set the coded format on OUTPUT via VIDIOC_S_FMT() sStreamDataFormat sdf = new sStreamDataFormat() { bufferType = eBufferType.VideoOutputMPlane, pix_mp = encodedFormat }; device.file.call(eControlCode.S_FMT, ref sdf); // Logger.logVerbose( "eControlCode.S_FMT completed OK for encoded format: {0}", sdf.pix_mp ); // 2 Allocate source (bytestream) buffers via VIDIOC_REQBUFS() on OUTPUT encoded = new EncodedQueue(device, encodedBuffersCount, ref encodedFormat); // 3 Start streaming on the OUTPUT queue via VIDIOC_STREAMON() device.startStreaming(eBufferType.VideoOutputMPlane); // Continue queuing/dequeuing bytestream buffers to/from the OUTPUT queue via VIDIOC_QBUF() and VIDIOC_DQBUF(). // The buffers will be processed and returned to the client in order, until required metadata to configure the CAPTURE queue are found. // This is indicated by the decoder sending a V4L2_EVENT_SOURCE_CHANGE event with changes set to V4L2_EVENT_SRC_CH_RESOLUTION. eventSubscription = new EventSubscription(device); // Linux kernel on Pi4 appears to be too old and does not implement that spec. The event never arrives, while the encoded buffers are stuck in the Queued state. // For this reason, we have to deal with dynamic resolution changes instead :-( // WaitForResolution.wait( device, encoded, reader, waitForResolutionTimeout ); }
// Called on decoder thread void iDecoderEvents.onDynamicResolutionChange() { sSelection selection = default; selection.type = eBufferType.VideoCaptureMPlane; selection.target = eSelectionTarget.Compose; device.file.call(eControlCode.G_SELECTION, ref selection); // Logger.logInfo( "selection: {0}", selection ); // Appears to be correct, i.e. matches what's in the PPS of the video if (selection.rect != decodedSize.cropRect) { throw new ApplicationException($"Linux failed to decode SPS from the video; SPS says the crop rectangle is { decodedSize.cropRect }, Linux decoded as { selection.rect }"); } sStreamDataFormat sdf = new sStreamDataFormat { bufferType = eBufferType.VideoCaptureMPlane }; device.file.call(eControlCode.G_FMT, ref sdf); Logger.logVerbose("Automatically selected format: {0}", sdf); decodedPixelFormat = sdf.pix_mp; colorFormat = sdf.pix_mp.colorFormat(); Logger.logInfo("Dynamic resolution change: {0}", colorFormat); return; // The following code causes endless loop of resolution changes, despite nothing being changed, really // state = eDecoderState.DrainRezChange; // The setup workflow in that V4L spec is BS, unfortunately :-( device.stopStreaming(eBufferType.VideoCaptureMPlane); int decodedBuffersCount = decoded.buffersCount; decoded.Dispose(); decoded = null; sImageFormatDescription format = device.findOutputFormat(); Logger.logVerbose("Picked the format \"{0}\", {1}, flags {2}", format.description, format.pixelFormat, format.flags); // Destroy the old decoded buffers sRequestBuffers rbDecoded = new sRequestBuffers() { type = eBufferType.VideoCaptureMPlane, memory = eMemory.MemoryMap }; device.file.call(eControlCode.REQBUFS, ref rbDecoded); // Fix a few things there sdf.pix_mp.pixelFormat = ePixelFormat.NV12; // This one is actually pre-selected, prolly because of the initial one we set in the captureSetup method sdf.pix_mp.quantization = eQuantization.FullRange; // Linux defaults to limited range, not what we want. device.file.call(eControlCode.S_FMT, ref sdf); // Logger.logVerbose( "Set format: {0}", sdf ); // Create new set of decoded buffers, same count as before decoded = new DecodedQueue(device, decodedBuffersCount); // Finally, resume the video device.startStreaming(eBufferType.VideoCaptureMPlane); decoded.enqueueAll(); }