diff --git a/av/codec/context.pyx b/av/codec/context.pyx index 7e79b8266f037b11638f934149e78ffcaff159bc..9f5dc92b14f21ad13587b6dd5e46e831fbf9ff2e 100644 --- a/av/codec/context.pyx +++ b/av/codec/context.pyx @@ -157,6 +157,7 @@ cdef class CodecContext(object): cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec): self.ptr = ptr + self.ptr.opaque = <void*>self if self.ptr.codec and codec and self.ptr.codec != codec: raise RuntimeError('Wrapping CodecContext with mismatched codec.') self.codec = wrap_codec(codec if codec != NULL else self.ptr.codec) diff --git a/av/video/codeccontext.pxd b/av/video/codeccontext.pxd index 9693caa9bb91e111c0f0e1f44ec485665648a7b2..d142b442f5b062db985ad7620b619e9cf5b3bfc1 100644 --- a/av/video/codeccontext.pxd +++ b/av/video/codeccontext.pxd @@ -1,4 +1,5 @@ +cimport libav as lib from av.codec.context cimport CodecContext from av.video.format cimport VideoFormat from av.video.frame cimport VideoFrame @@ -7,8 +8,9 @@ from av.video.reformatter cimport VideoReformatter cdef class VideoCodecContext(CodecContext): - cdef VideoFormat _format - cdef _build_format(self) + cdef lib.AVPixelFormat _preferred_format + + cdef VideoFormat _last_format cdef int last_w cdef int last_h diff --git a/av/video/codeccontext.pyx b/av/video/codeccontext.pyx index f78e850e5c26ff08365a6a95436c4d16ce5a0eb3..28451904f19af71e8892098d9bbf9d99daf164c9 100644 --- a/av/video/codeccontext.pyx +++ b/av/video/codeccontext.pyx @@ -11,6 +11,14 @@ from av.video.format cimport get_video_format, VideoFormat from av.video.frame cimport VideoFrame, alloc_video_frame from av.video.reformatter cimport VideoReformatter +cdef lib.AVPixelFormat get_format_callback(lib.AVCodecContext *ptr, const lib.AVPixelFormat *fmt): + cdef lib.AVPixelFormat pref = (<VideoCodecContext>ptr.opaque)._preferred_format + cdef int current = 0 + while fmt[current] != -1: + if fmt[current] == pref: + return pref + current += 1 + return lib.avcodec_default_get_format(ptr, fmt) cdef class VideoCodecContext(CodecContext): @@ -20,8 +28,10 @@ cdef class VideoCodecContext(CodecContext): cdef _init(self, lib.AVCodecContext *ptr, const lib.AVCodec *codec): CodecContext._init(self, ptr, codec) # TODO: Can this be `super`? - self._build_format() + self._last_format = None self.encoded_frame_count = 0 + self._preferred_format = lib.AV_PIX_FMT_NONE + self.ptr.get_format = get_format_callback cdef _set_default_time_base(self): self.ptr.time_base.num = self.ptr.framerate.den or 1 @@ -36,7 +46,7 @@ cdef class VideoCodecContext(CodecContext): # Reformat if it doesn't match. if ( - vframe.format.pix_fmt != self._format.pix_fmt or + vframe.format.pix_fmt != self.ptr.pix_fmt or vframe.width != self.ptr.width or vframe.height != self.ptr.height ): @@ -46,7 +56,7 @@ cdef class VideoCodecContext(CodecContext): vframe, self.ptr.width, self.ptr.height, - self._format, + self.format, ) # There is no pts, so create one. @@ -65,18 +75,30 @@ cdef class VideoCodecContext(CodecContext): cdef VideoFrame vframe = frame vframe._init_user_attributes() - cdef _build_format(self): - self._format = get_video_format(<lib.AVPixelFormat>self.ptr.pix_fmt, self.ptr.width, self.ptr.height) - property format: def __get__(self): - return self._format + if not ( + self._last_format != None and + self._last_format.pix_fmt == self.ptr.pix_fmt and + self._last_format.width == self.ptr.width and + self._last_format.height == self.ptr.height + ): + self._last_format = get_video_format(<lib.AVPixelFormat>self.ptr.pix_fmt, self.ptr.width, self.ptr.height) + return self._last_format def __set__(self, VideoFormat format): self.ptr.pix_fmt = format.pix_fmt self.ptr.width = format.width self.ptr.height = format.height - self._build_format() # Kinda wasteful. + + property preferred_format: + def __get__(self): + if self._preferred_format == lib.AV_PIX_FMT_NONE: + return None + return lib.av_get_pix_fmt_name(self._preferred_format) + + def __set__(self, value): + self._preferred_format = lib.av_get_pix_fmt(value) property width: def __get__(self): @@ -84,7 +106,6 @@ cdef class VideoCodecContext(CodecContext): def __set__(self, unsigned int value): self.ptr.width = value - self._build_format() property height: def __get__(self): @@ -92,16 +113,15 @@ cdef class VideoCodecContext(CodecContext): def __set__(self, unsigned int value): self.ptr.height = value - self._build_format() # TODO: Replace with `format`. property pix_fmt: def __get__(self): - return self._format.name + if self.format != None: + return self.format.name def __set__(self, value): self.ptr.pix_fmt = lib.av_get_pix_fmt(value) - self._build_format() property framerate: """ diff --git a/include/libavcodec/avcodec.pxd b/include/libavcodec/avcodec.pxd index 92d860d650aea9a4bc885642448570c79b63fda4..0490c7a56ff533e5a27a218dec9f955a49315786 100644 --- a/include/libavcodec/avcodec.pxd +++ b/include/libavcodec/avcodec.pxd @@ -202,6 +202,7 @@ cdef extern from "libavcodec/avcodec.h" nogil: int coded_height AVPixelFormat pix_fmt + AVPixelFormat get_format(AVCodecContext *ctx, const AVPixelFormat *fmt) AVRational sample_aspect_ratio int gop_size # The number of pictures in a group of pictures, or 0 for intra_only. int max_b_frames @@ -245,6 +246,8 @@ cdef extern from "libavcodec/avcodec.h" nogil: cdef AVCodecDescriptor* avcodec_descriptor_get (AVCodecID id) cdef AVCodecDescriptor* avcodec_descriptor_get_by_name (char *name) + cdef AVPixelFormat avcodec_default_get_format(AVCodecContext *ctx, const AVPixelFormat *fmt) + cdef char* avcodec_get_name(AVCodecID id) cdef char* av_get_profile_name(AVCodec *codec, int profile) diff --git a/tests/test_codec_context.py b/tests/test_codec_context.py index b213c04750e5e135135c40213b8101d92173fcee..a6d0156861a4cc4712add924374c18776658a9c8 100644 --- a/tests/test_codec_context.py +++ b/tests/test_codec_context.py @@ -51,6 +51,26 @@ class TestCodecContext(TestCase): # This one parses into many small packets. self._assert_parse('mpeg2video', fate_suite('mpeg2/mpeg2_field_encoding.ts')) + def test_format_override(self): + # Some decoders may override pix_fmt + ctx = Codec('gif', 'r').create() + ctx.pix_fmt = 'yuv420p' + ctx.width = 500 + ctx.height = 500 + ctx.open() + self.assertNotEqual(ctx.pix_fmt, 'yuv420p') + + def test_format_not_set(self): + ctx = Codec('png', 'w').create() + self.assertEqual(ctx.format, None) + self.assertEqual(ctx.pix_fmt, None) + ctx.pix_fmt = 'bgra' + self.assertEqual(ctx.format.name, 'bgra') + self.assertEqual(ctx.pix_fmt, 'bgra') + ctx.pix_fmt = 'invalid' + self.assertEqual(ctx.format, None) + self.assertEqual(ctx.pix_fmt, None) + def _assert_parse(self, codec_name, path): fh = av.open(path)