[Libav-user] Is a custom I/O read callback allowed to overwrite the buffer if it returns AVERROR_EOF?
Jyrki Vesterinen
jyrkive at nekonyansoft.com
Tue Jul 5 21:37:54 EEST 2022
Hello,
I am developing a game with the Unity game engine. For various reasons, our
game uses custom asset archives instead of Unity's native assets, and
utilizes FFmpeg for decoding images and audio inside these custom archives.
To allow FFmpeg to read data from these encrypted asset archives, we give
it custom I/O read callbacks implemented in C# and made accessible to
native code via reverse P/Invoke. The C# delegate for the read callback is
as follows:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate int ReadPacket(IntPtr opaque, [Out,
MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] byte[] buf, int
bufSize);
The "Out" is a directional attribute informing the C# compiler that the
caller will use contents of the array but the callee doesn't care about its
initial content. For more information, see
https://docs.microsoft.com/en-us/previous-versions/dotnet/netframework-4.0/77e6taeh(v=vs.100)?redirectedfrom=MSDN
This means that FFmpeg will always see what my read callback has explicitly
written to the buffer, but whatever was in the buffer before FFmpeg called
the callback is not guaranteed to be preserved, even if the read operation
didn't fill the whole buffer or even read any bytes at all.
I have discovered that one of our Ogg Vorbis music tracks fails to play
correctly due to this. Logging FFmpeg's calls to the callbacks shows this
(note: I have given FFmpeg a 1MB buffer):
1. ReadPacket, 1048576 bytes. The length of the entire track is 1029379
bytes, so we write it into the buffer and return 1029379.
2. Seek, with "whence" set to AVSEEK_SIZE. We return 1029379.
3. ReadPacket, 1048576 bytes. Since we're already at the end of the file,
we return AVERROR_EOF and P/Invoke overwrites the entire buffer.
After this, oggdec.c:ogg_read_page() fails to find a sync word and FFmpeg
returns AVERROR_INVALIDDATA.
I can easily fix this by adding the "In" directional attribute, i.e.
turning the C# delegate into
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate int ReadPacket(IntPtr opaque, [In, Out,
MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] byte[] buf, int
bufSize);
This instructs P/Invoke to preserve the original content of the array when
calling the function, and thus it remains after the call too. However, it
comes with a performance penalty.
My question is: is the read callback supposed to preserve content of the
array, or is it a bug that the Ogg demuxer assumes it to have remained
after calling the callback if it returns AVERROR_EOF?
--
Jyrki Vesterinen
Programmer
NekoNyan Ltd
More information about the Libav-user
mailing list