ffmpeginteropx / FFmpegInteropX

FFmpeg decoding library for Windows 10 UWP and WinUI 3 Apps
Apache License 2.0
211 stars 53 forks source link

Custom Stream with async read #437

Closed sakib1361 closed 4 weeks ago

sakib1361 commented 4 weeks ago

Enhancement Right now, custom streams work as any streams can be converted to IRandomAceessStream. It can then be utilized via the library given that the underlying streams are seekable. But in the scenario where the streams readasync/read function has to wait for a few second to cache it up the UI seems to be freezed.

Sample ` public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { var startOffset = _start + offset + Position; int piece = (int)(startOffset / _piece);

if (_chunks.Contains(piece) == false)
{
    int pieceEnd = (int)((startOffset + offset + Position + 10*1024*1024)/_piece);
    pieceEnd = Math.Max(piece + 1, pieceEnd);
    _handler.FocusPieces(piece, pieceEnd, _lastPiece);
    while (_handler.HasPiece(piece) == false)
    {
        await Task.Delay(2000, cancellationToken).ConfigureAwait(false);
    }
    _chunks.Add(piece);
}
return await _fileStream.ReadAsync(buffer, offset, count, cancellationToken);

} ` This is the actual code snippet from the implementation where the handler downloads and saves to file via chunks. While checking for piece, I have used a task.delay. The outsider functions HasPiece and FocusPieces are almost instant. This behaviour can also be observed removing the Read piece code and just introducing a delay before returning _fileStream.ReadAsync() I have also tried with overriding stream.read() but that's also freezes the UI. I am assuming this is the part which actually reads data from stream.

HRESULT hr = mss->fileStreamData->Read(buf, bufSize, &bytesRead);

Expected behaviour: The main UI shouldn't freeze on streams which takes time to return. If mss is bypassed via the original new MediaPlaybackItem(MediaSource.CreateFromStream()), the app seems to work as it should be honouring the async delays.

Possible implementation: https://ffmpeg.org/doxygen/trunk/async_8c_source.html#l00386

I would assume this might be complicated to implement. In that case, we can mark this as solved.

brabebhin commented 4 weeks ago

Hi,

Are you sure the problem isn't caused by the stream's custom implementation marshalling the read in the UI thread?

Our internal reads are done on background threads on which the MF callbacks into MSS are marshaled. It's very unlikely this will end up in the UI thread.

Also if the stream is written in c# you'll run into performance issues caused by GC freezes.

A full runnable sample might be easier to understand.

Thanks

sakib1361 commented 4 weeks ago

You are absolutely right. The thread issue was mine. There was an info dispatcherque along with the reader implemented. Thanks a lot. To be noted, not sure of the performance impact but the stream implemented in the C# works pretty well. No garbage freeze at all. The custom stream is nothing but a filestream with a lock to check if it has been successfully downloaded.

Detail (Not important) I had a dispatcherque which shows progress and stat info whenever there was a buffer. As I found out, I couldn't simultaneously lock both ReaderAsync and Infos calling like mss.CurrentVideoStream.HardwareDecoderStatus both on the same thread. (Bad design) Detail End

Again, thanks a lot for the fast response. As it was working on MediaSource, what I thought was wrong.

brabebhin commented 4 weeks ago

As long as you just pass the read operation to the base Stream implementation, you should be fine with the gc, the problem comes when you read actual bytes in c#, using byte arrays that you allocate with the new operator.

In the end you'll probably be able to intercept the read calls on the Stream that are coming from ffmpeginteropx, do not attempt to keep references to the byte arrays being passed there, as we recycle them and your data will be dirty.