OK, here is the problem. AVI files written by Media Foundation are setting the dwTotalFrames field of the AVIMAINHEADER structure in the AVI file to 10x the correct count.
So, if I create a sink, call WriteSample() once. This field will be set to 10 frames. If I write 50 frames, it will be set to 500 frames.
If I hexedit the AVI file to have 10x fewer frames, then any app can load the AVI correctly (no index problems). Which makes sense, since with the 1 frame example, the index only has 1 entry in it (not 10 entries as dwTotalFrames would lead you to believe).
I believe Media Foundation is calculating the dwTotalFrames incorrectly, because if I set the duration for the single frame to the smallest possible duration, I still get 10 frames. If I set the duration to twice as long as it should be, then Media Foundation creates an AVI file with 20 frames! It is always exactly 10x off.
My guess is that someone typed in a 1000000 instead of a 10000000 somewhere...