Skip to content

Conversation

@jlaine
Copy link
Member

@jlaine jlaine commented Mar 15, 2022

No description provided.

@jlaine jlaine marked this pull request as draft March 15, 2022 16:25
@jlaine jlaine force-pushed the no-stream-codeccontext branch from f1e581e to 71478e3 Compare March 15, 2022 20:45
@jlaine jlaine mentioned this pull request Mar 15, 2022
6 tasks
@jlaine jlaine force-pushed the no-stream-codeccontext branch from 71478e3 to a611f51 Compare March 15, 2022 22:54
@jlaine
Copy link
Member Author

jlaine commented Mar 15, 2022

@mikeboers This is progressing better than I thought. One interesting aspect is that we now hit the exact same failures as the ones encountered against FFmpeg 4.4.

@jlaine jlaine force-pushed the no-stream-codeccontext branch from a611f51 to 7c6375a Compare March 15, 2022 23:17
@jlaine jlaine changed the title [streams] stop using deprecated Stream.codec, it's gone in FFmpeg 5 Add support for FFmpeg 5.0 Mar 15, 2022
@jlaine jlaine force-pushed the no-stream-codeccontext branch 4 times, most recently from 0673d00 to 5c954d8 Compare March 22, 2022 07:41
@uvjustin
Copy link
Contributor

@jlaine Using a separate AVCodecContext for PyAV is the same approach I took when I attempted a refactor. I thought there would be more issues with PyAV using a AVCodecContext distinct from the internal one held by AVStream, but it doesn't seem like this causes too many problems.
It does seem like framerate in AVCodecContext is not passed through AVCodecParameters when copying back and forth, and there seem to be a few different framerate/time_base fields between AVCodecContext and AVStream, and I think we need to take care to maintain consistency there.
BTW, the subtitles test issue with ffmpeg 5.0 seems to be related to the deprecation of the old ASS format with timing here: https://ffmpeg.org/pipermail/ffmpeg-cvslog/2016-February/098534.html .

to_avrational(rate or 24, &codec_context.framerate)

stream.avg_frame_rate = codec_context.framerate
stream.time_base = codec_context.time_base
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realize this was in the existing code, but is codec_context.time_base ever set before here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think time_base gets initialized to a sane value, but it would be worth double checking. I really appreciate you looking at this PR, feel free to tinker and suggest changes as I'm not 100% sure of what I'm doing! When in doubt I look at how FFmpeg (CLI) does it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I will also tinker with it a bit when I get a chance. It doesn't seem too far off. I think some of the failing tests may actually be the result of existing mismatches between PyAV and ffmpeg. It's hard to get everything to align fully as some of the fields like framerate can get set by ffmpeg in a number of different places depending on the container/codec used.

@jlaine
Copy link
Member Author

jlaine commented Mar 22, 2022

NOTE: I noticed there is an issue in close_output, because Container.streams can already be partially destroyed, so calling it from OutputContainer.__dealloc__ is not reliable. I'm not sure I can do much than emitting a warning if this happens.

I've probably also introduced a memory leak, I need to free the AVCodecContext. EDIT: no I did not, as the AVCodecContext is wrapped in a Python CodecContext which frees the C structure.

@jlaine jlaine force-pushed the no-stream-codeccontext branch from 5c954d8 to 6b1295f Compare March 24, 2022 07:17
@uvjustin
Copy link
Contributor

For the subtitles test failing on 5.0, I think we can just change the test to also accept the regular ASS format without the "Dialogue: " prefix and the two timestamps (basically just allow what it's failing on now).

@jlaine
Copy link
Member Author

jlaine commented Mar 24, 2022

For the subtitles test failing on 5.0, I think we can just change the test to also accept the regular ASS format without the "Dialogue: " prefix and the two timestamps (basically just allow what it's failing on now).

No I don't think that's the issue. I've tried changing the format, which does strip away the timings from the output. However I think it masks the underlying issue which is that the timings look plain wrong.

@uvjustin
Copy link
Contributor

uvjustin commented Mar 24, 2022

The current ASS format doesn't include the timings in the output though. Are you checking them in SubtitleProxy?
Edit:
If you are referring to the 0.97 and 2.54 subtitle times embedded in the ASS string, those are only in the deprecated ASS format, but we can check them by adding:

        self.assertIsInstance(subs[0], SubtitleSet)
        self.assertEqual(subs[0].pts, 970000)
        self.assertEqual(subs[0].start_display_time, 0)
        self.assertEqual(subs[0].end_display_time, 1570)

pts is in lib.AV_TIME_BASE and start/end display times are in ms, we have:
start == subs[0].pts / lib.AV_TIME_BASE + subs[0].start_display_time / 1000 == 0.97
end == subs[0].pts / lib.AV_TIME_BASE + subs[0].end_display_time / 1000 == 2.54

@jlaine jlaine force-pushed the no-stream-codeccontext branch 2 times, most recently from ae1619c to 5eaa500 Compare March 27, 2022 15:17
@jlaine
Copy link
Member Author

jlaine commented Mar 27, 2022

The current ASS format doesn't include the timings in the output though. Are you checking them in SubtitleProxy? Edit: If you are referring to the 0.97 and 2.54 subtitle times embedded in the ASS string, those are only in the deprecated ASS format, but we can check them by adding:

        self.assertIsInstance(subs[0], SubtitleSet)
        self.assertEqual(subs[0].pts, 970000)
        self.assertEqual(subs[0].start_display_time, 0)
        self.assertEqual(subs[0].end_display_time, 1570)

OK you're exactly right, so I have reworked the tests as suggested to explicitly check timings. I've also made "ass" format the default instead of "ass_with_timings" to get consistent behaviour across FFmpeg versions.

That leaves the other failures related to rates.

@jlaine jlaine force-pushed the no-stream-codeccontext branch 6 times, most recently from 557cc2b to 6c70bf7 Compare April 1, 2022 08:25
@jlaine jlaine marked this pull request as ready for review April 1, 2022 08:26
@jlaine jlaine force-pushed the no-stream-codeccontext branch from 6c70bf7 to 5695e7e Compare April 1, 2022 11:19
@jlaine jlaine force-pushed the no-stream-codeccontext branch 2 times, most recently from 23fd0dc to e075808 Compare April 1, 2022 13:20
@jlaine jlaine force-pushed the no-stream-codeccontext branch 2 times, most recently from 53d8da3 to 428e50f Compare April 20, 2022 14:59
@uvjustin
Copy link
Contributor

@jlaine This looks pretty good and the tests seem to be passing.
I'll make a build against ffmpeg 5.0 and test it against our usage in Home Assistant.

@jlaine
Copy link
Member Author

jlaine commented Jun 17, 2022

I had put off merging this to see if any bug-fixes needed to land in the 9.x branch, as it will be the last one supporting FFmpeg < 4.3. It looks as though we're in a good place, so we're close to the point where this PR should land.

One thing that bothers me is that we're hardly testing anything at all on Windows (i.e. only FFmpeg 4.3). Conda seems to have dropped the ball on packaging FFmpeg for Windows..

@uvjustin
Copy link
Contributor

Maybe we can try to use the Windows binaries from https://github.com/BtbN/FFmpeg-Builds/releases instead of from Conda?

Copy link
Contributor

@uvjustin uvjustin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty good. I left some comments with a few things I noticed when getting a stopgap release ready on the ha-av fork.

if codec:
# allocate and initialise decoder
codec_context = lib.avcodec_alloc_context3(codec)
err_check(lib.avcodec_parameters_to_context(codec_context, stream.codecpar))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avcodec_parameters_to_context does not set the framerate field of AVCodecContext: https://www.ffmpeg.org/doxygen/trunk/codec__par_8c_source.html#l00182
As Stream.rate/Stream.framerate relies on getattr pass through to its codec_context's rate/framerate, this means Stream.rate/Stream.framerate are no longer valid for input video streams.
Two ways we can remedy this:

  1. We can set the codec_context's framerate here and continue to rely on this passthrough feature.
  2. We can add rate/framerate properties directly to VideoStream.
    Note that AudioStream does not have this issue as the sample_rate field gets copied properly.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not even sure we should be doing anything:

https://www.ffmpeg.org/doxygen/trunk/structAVCodecContext.html#a4d08b297e97eefd66c714df4fff493c8

decoding: For codecs that store a framerate value in the compressed bitstream, the decoder may export it here. { 0, 1} when unknown.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure it's not always set. It was causing a failure in our test suite that I remedied using the 2nd option above.
However, what you said actually implies we should rely on the first option above (passthrough), because the decoder may export it when decoding the bitstream.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we've reached the point where not merging is actively hurting the project, so I'm going to hit the button. Would you mind opening an issue to discuss the "framerate" point you made?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure

jlaine added 2 commits June 25, 2022 16:42
We now allocate and populate an AVCodecContext ourselves.
avcodec_copy_context is also gone, so stop using it.

We relax the Stream.average_rate tests for older FFmpeg, as the videos
output by these older FFmpeg's seem to give a slightly wrong FPS since
the switch to our own AVCodecContext.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants