From 58669c12812b71838fcf455f2f7036406876ae6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jeremy=20Lain=C3=A9?= <jeremy.laine@m4x.org> Date: Thu, 4 Oct 2018 10:54:14 +0200 Subject: [PATCH 1/3] Always use send / receive mechanism for encoding It has been available since at least FFmpeg 3.1. Only subtitles require using the legacy APIs, but we don't support encoding subtitles anyway. --- av/audio/codeccontext.pyx | 21 ------------------- av/codec/context.pxd | 3 +-- av/codec/context.pyx | 37 +++++----------------------------- av/video/codeccontext.pyx | 24 ---------------------- include/libavcodec/avcodec.pxd | 14 ------------- 5 files changed, 6 insertions(+), 93 deletions(-) diff --git a/av/audio/codeccontext.pyx b/av/audio/codeccontext.pyx index b2a0546..0026f8b 100644 --- a/av/audio/codeccontext.pyx +++ b/av/audio/codeccontext.pyx @@ -55,27 +55,6 @@ cdef class AudioCodecContext(CodecContext): return frames - cdef _encode(self, Frame frame): - """Encodes a frame of audio, returns a packet if one is ready. - The output packet does not necessarily contain data for the most recent frame, - as encoders can delay, split, and combine input frames internally as needed. - If called with with no args it will flush out the encoder and return the buffered - packets until there are none left, at which it will return None. - """ - - cdef Packet packet = Packet() - cdef int got_packet = 0 - - err_check(lib.avcodec_encode_audio2( - self.ptr, - &packet.struct, - frame.ptr if frame is not None else NULL, - &got_packet, - )) - - if got_packet: - return packet - cdef Frame _alloc_next_frame(self): return alloc_audio_frame() diff --git a/av/codec/context.pxd b/av/codec/context.pxd index 7f62350..4290d6f 100644 --- a/av/codec/context.pxd +++ b/av/codec/context.pxd @@ -42,7 +42,7 @@ cdef class CodecContext(object): cdef _set_default_time_base(self) # Wraps both versions of the transcode API, returning lists. - cpdef encode(self, Frame frame=?, unsigned int count=?, bint prefer_send_recv=?) + cpdef encode(self, Frame frame=?) cpdef decode(self, Packet packet=?, unsigned int count=?, bint prefer_send_recv=?) # Used by both transcode APIs to setup user-land objects. @@ -53,7 +53,6 @@ cdef class CodecContext(object): cdef _setup_decoded_frame(self, Frame, Packet) # Implemented by children for the encode/decode API. - cdef _encode(self, Frame frame) cdef _decode(self, lib.AVPacket *packet, int *data_consumed) # Implemented by base for the generic send/recv API. diff --git a/av/codec/context.pyx b/av/codec/context.pyx index f2a4eac..b9869ef 100644 --- a/av/codec/context.pyx +++ b/av/codec/context.pyx @@ -308,12 +308,14 @@ cdef class CodecContext(object): if not res: return packet - cpdef encode(self, Frame frame=None, unsigned int count=0, bint prefer_send_recv=True): + cpdef encode(self, Frame frame=None): """Encode a list of :class:`.Packet` from the given :class:`.Frame`.""" + if self.ptr.codec_type not in [lib.AVMEDIA_TYPE_VIDEO, lib.AVMEDIA_TYPE_AUDIO]: + raise NotImplementedError('Encoding is only supported for audio and video.') + self.open(strict=False) - cdef bint is_flushing = frame is None frames = self._prepare_frames_for_encode(frame) # Assert the frames are in our time base. @@ -323,36 +325,10 @@ cdef class CodecContext(object): frame._rebase_time(self.ptr.time_base) res = [] - - if ( - prefer_send_recv and - lib.PYAV_HAVE_AVCODEC_SEND_PACKET and - ( - self.ptr.codec_type == lib.AVMEDIA_TYPE_VIDEO or - self.ptr.codec_type == lib.AVMEDIA_TYPE_AUDIO - ) - ): - for frame in frames: - for packet in self._send_frame_and_recv(frame): - self._setup_encoded_packet(packet) - res.append(packet) - return res - - for frame in frames: - packet = self._encode(frame) - if packet: + for packet in self._send_frame_and_recv(frame): self._setup_encoded_packet(packet) res.append(packet) - - while is_flushing and (not count or count > len(res)): - packet = self._encode(None) - if packet: - self._setup_encoded_packet(packet) - res.append(packet) - else: - break - return res cdef _setup_encoded_packet(self, Packet packet): @@ -365,9 +341,6 @@ cdef class CodecContext(object): # are off! packet._time_base = self.ptr.time_base - cdef _encode(self, Frame frame): - raise NotImplementedError('Base CodecContext cannot encode frames.') - cpdef decode(self, Packet packet=None, unsigned int count=0, bint prefer_send_recv=True): """Decode a list of :class:`.Frame` from the given :class:`.Packet`. diff --git a/av/video/codeccontext.pyx b/av/video/codeccontext.pyx index 1d804ec..e70326f 100644 --- a/av/video/codeccontext.pyx +++ b/av/video/codeccontext.pyx @@ -60,30 +60,6 @@ cdef class VideoCodecContext(CodecContext): return [vframe] - cdef _encode(self, Frame frame): - """Encodes a frame of video, returns a packet if one is ready. - - The output packet does not necessarily contain data for the most recent frame, - as encoders can delay, split, and combine input frames internally as needed. - If called with with no args it will flush out the encoder and return the buffered - packets until there are none left, at which it will return None. - - """ - - cdef VideoFrame vframe = frame - - - cdef Packet packet = Packet() - cdef int got_packet - - if vframe is not None: - ret = err_check(lib.avcodec_encode_video2(self.ptr, &packet.struct, vframe.ptr, &got_packet)) - else: - ret = err_check(lib.avcodec_encode_video2(self.ptr, &packet.struct, NULL, &got_packet)) - - if got_packet: - return packet - cdef Frame _alloc_next_frame(self): return alloc_video_frame() diff --git a/include/libavcodec/avcodec.pxd b/include/libavcodec/avcodec.pxd index d8d913e..f452e4a 100644 --- a/include/libavcodec/avcodec.pxd +++ b/include/libavcodec/avcodec.pxd @@ -286,20 +286,6 @@ cdef extern from "libavcodec/avcodec.pyav.h" nogil: AVPacket *packet, ) - cdef int avcodec_encode_audio2( - AVCodecContext *ctx, - AVPacket *avpkt, - AVFrame *frame, - int *got_packet_ptr - ) - - cdef int avcodec_encode_video2( - AVCodecContext *ctx, - AVPacket *avpkt, - AVFrame *frame, - int *got_packet_ptr - ) - cdef int avcodec_fill_audio_frame( AVFrame *frame, int nb_channels, -- GitLab From 85b6562c2bb7f640676a0f1b660665af1b4c67a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jeremy=20Lain=C3=A9?= <jeremy.laine@m4x.org> Date: Thu, 4 Oct 2018 11:58:50 +0200 Subject: [PATCH 2/3] Always use send / recv mechanism for decoding It has been available since at least FFmpeg 3.1. For subtitle, we mimick the API. --- av/audio/codeccontext.pyx | 17 -------- av/codec/context.pxd | 5 +-- av/codec/context.pyx | 69 +++---------------------------- av/packet.pyx | 6 +-- av/stream.pyx | 4 +- av/subtitles/codeccontext.pyx | 14 +++++-- av/video/codeccontext.pyx | 39 ----------------- include/libav.pxd | 2 - include/libavcodec/avcodec.pxd | 14 ------- include/libavcodec/avcodec.pyav.h | 11 ----- setup.py | 1 - tests/test_subtitles.py | 10 +---- 12 files changed, 24 insertions(+), 168 deletions(-) diff --git a/av/audio/codeccontext.pyx b/av/audio/codeccontext.pyx index 0026f8b..aa4c15c 100644 --- a/av/audio/codeccontext.pyx +++ b/av/audio/codeccontext.pyx @@ -58,23 +58,6 @@ cdef class AudioCodecContext(CodecContext): cdef Frame _alloc_next_frame(self): return alloc_audio_frame() - cdef _decode(self, lib.AVPacket *packet, int *data_consumed): - - if not self.next_frame: - self.next_frame = alloc_audio_frame() - - cdef int completed_frame = 0 - data_consumed[0] = err_check(lib.avcodec_decode_audio4(self.ptr, self.next_frame.ptr, &completed_frame, packet)) - if not completed_frame: - return - - cdef AudioFrame frame = self.next_frame - self.next_frame = None - - frame._init_user_attributes() - - return frame - cdef _setup_decoded_frame(self, Frame frame, Packet packet): CodecContext._setup_decoded_frame(self, frame, packet) cdef AudioFrame aframe = frame diff --git a/av/codec/context.pxd b/av/codec/context.pxd index 4290d6f..6bc9d7c 100644 --- a/av/codec/context.pxd +++ b/av/codec/context.pxd @@ -43,7 +43,7 @@ cdef class CodecContext(object): # Wraps both versions of the transcode API, returning lists. cpdef encode(self, Frame frame=?) - cpdef decode(self, Packet packet=?, unsigned int count=?, bint prefer_send_recv=?) + cpdef decode(self, Packet packet=?) # Used by both transcode APIs to setup user-land objects. # TODO: Remove the `Packet` from `_setup_decoded_frame` (because flushing @@ -52,9 +52,6 @@ cdef class CodecContext(object): cdef _setup_encoded_packet(self, Packet) cdef _setup_decoded_frame(self, Frame, Packet) - # Implemented by children for the encode/decode API. - cdef _decode(self, lib.AVPacket *packet, int *data_consumed) - # Implemented by base for the generic send/recv API. # Note that the user cannot send without recieving. This is because # _prepare_frames_for_encode may expand a frame into multiple (e.g. when diff --git a/av/codec/context.pyx b/av/codec/context.pyx index b9869ef..25c8f92 100644 --- a/av/codec/context.pyx +++ b/av/codec/context.pyx @@ -341,7 +341,7 @@ cdef class CodecContext(object): # are off! packet._time_base = self.ptr.time_base - cpdef decode(self, Packet packet=None, unsigned int count=0, bint prefer_send_recv=True): + cpdef decode(self, Packet packet=None): """Decode a list of :class:`.Frame` from the given :class:`.Packet`. If the packet is None, the buffers will be flushed. This is useful if @@ -355,66 +355,12 @@ cdef class CodecContext(object): self.open(strict=False) - if ( - prefer_send_recv and - lib.PYAV_HAVE_AVCODEC_SEND_PACKET and - ( - self.ptr.codec_type == lib.AVMEDIA_TYPE_VIDEO or - self.ptr.codec_type == lib.AVMEDIA_TYPE_AUDIO - ) - ): - res = [] - for frame in self._send_packet_and_recv(packet): + res = [] + for frame in self._send_packet_and_recv(packet): + if isinstance(frame, Frame): self._setup_decoded_frame(frame, packet) - res.append(frame) - return res - - if packet is None: - packet = Packet() # Makes our control flow easier. - - cdef int data_consumed = 0 - cdef list decoded_objs = [] - - cdef uint8_t *original_data = packet.struct.data - cdef int original_size = packet.struct.size - - cdef bint is_flushing = not (packet.struct.data and packet.struct.size) - - # Keep decoding while there is data in this packet. - while is_flushing or packet.struct.size > 0: - - if is_flushing: - packet.struct.data = NULL - packet.struct.size = 0 - - decoded = self._decode(&packet.struct, &data_consumed) - packet.struct.data += data_consumed - packet.struct.size -= data_consumed - - if decoded: - - if isinstance(decoded, Frame): - self._setup_decoded_frame(decoded, packet) - decoded_objs.append(decoded) - - # Sometimes we will error if we try to flush the stream - # (e.g. MJPEG webcam streams), and so we must be able to - # bail after the first, even though buffers may build up. - if count and len(decoded_objs) >= count: - break - - # Sometimes there are no frames, and no data is consumed, and this - # is ok. However, no more frames are going to be pulled out of here. - # (It is possible for data to not be consumed as long as there are - # frames, e.g. during flushing.) - elif not data_consumed: - break - - # Restore the packet. - packet.struct.data = original_data - packet.struct.size = original_size - - return decoded_objs + res.append(frame) + return res cdef _setup_decoded_frame(self, Frame frame, Packet packet): @@ -427,9 +373,6 @@ cdef class CodecContext(object): frame.index = self.ptr.frame_number - 1 - cdef _decode(self, lib.AVPacket *packet, int *data_consumed): - raise NotImplementedError('Base CodecContext cannot decode packets.') - property name: def __get__(self): return self.codec.name diff --git a/av/packet.pyx b/av/packet.pyx index ad1fabf..3f2377c 100644 --- a/av/packet.pyx +++ b/av/packet.pyx @@ -101,15 +101,15 @@ cdef class Packet(Buffer): self._time_base = dst - def decode(self, count=0): + def decode(self): """Decode the data in this packet into a list of Frames.""" - return self._stream.decode(self, count) + return self._stream.decode(self) def decode_one(self): """Decode the first frame from this packet. Returns ``None`` if there is no frame.""" - res = self._stream.decode(self, count=1) + res = self._stream.decode(self) return res[0] if res else None property stream_index: diff --git a/av/stream.pyx b/av/stream.pyx index 359848b..eeed9d4 100644 --- a/av/stream.pyx +++ b/av/stream.pyx @@ -124,8 +124,8 @@ cdef class Stream(object): packet.struct.stream_index = self._stream.index return packets - def decode(self, packet=None, count=0): - return self.codec_context.decode(packet, count) + def decode(self, packet=None): + return self.codec_context.decode(packet) def seek(self, offset, whence='time', backward=True, any_frame=False): """seek(offset, whence='time', backward=True, any_frame=False) diff --git a/av/subtitles/codeccontext.pyx b/av/subtitles/codeccontext.pyx index 64c4e21..ef2dff7 100644 --- a/av/subtitles/codeccontext.pyx +++ b/av/subtitles/codeccontext.pyx @@ -1,17 +1,23 @@ cimport libav as lib from av.frame cimport Frame +from av.packet cimport Packet from av.subtitles.subtitle cimport SubtitleProxy, SubtitleSet from av.utils cimport err_check cdef class SubtitleCodecContext(CodecContext): - cdef _decode(self, lib.AVPacket *packet, int *data_consumed): - + cdef _send_packet_and_recv(self, Packet packet): cdef SubtitleProxy proxy = SubtitleProxy() cdef int got_frame = 0 - data_consumed[0] = err_check(lib.avcodec_decode_subtitle2(self.ptr, &proxy.struct, &got_frame, packet)) + err_check(lib.avcodec_decode_subtitle2( + self.ptr, + &proxy.struct, + &got_frame, + &packet.struct if packet else NULL)) if got_frame: - return SubtitleSet(proxy) + return [SubtitleSet(proxy)] + else: + return [] diff --git a/av/video/codeccontext.pyx b/av/video/codeccontext.pyx index e70326f..ceb12a1 100644 --- a/av/video/codeccontext.pyx +++ b/av/video/codeccontext.pyx @@ -68,45 +68,6 @@ cdef class VideoCodecContext(CodecContext): cdef VideoFrame vframe = frame vframe._init_user_attributes() - cdef _decode(self, lib.AVPacket *packet, int *data_consumed): - - # Create a frame if we don't have one ready. - if not self.next_frame: - self.next_frame = alloc_video_frame() - - # Decode video into the frame. - cdef int completed_frame = 0 - - cdef int result - - with nogil: - result = lib.avcodec_decode_video2(self.ptr, self.next_frame.ptr, &completed_frame, packet) - data_consumed[0] = err_check(result) - - if not completed_frame: - return - - # Check if the frame size has changed so that we can always have a - # SwsContext that is ready to go. - if self.last_w != self.ptr.width or self.last_h != self.ptr.height: - self.last_w = self.ptr.width - self.last_h = self.ptr.height - # TODO codec-ctx: Stream would calculate self.buffer_size here. - self.reformatter = VideoReformatter() - - # We are ready to send this one off into the world! - cdef VideoFrame frame = self.next_frame - self.next_frame = None - - # Share our SwsContext with the frames. Most of the time they will end - # up using the same settings as each other, so it makes sense to cache - # it like this. - # TODO codec-ctx: Stream did this. - #frame.reformatter = self.reformatter - - return frame - - cdef _build_format(self): self._format = get_video_format(<lib.AVPixelFormat>self.ptr.pix_fmt, self.ptr.width, self.ptr.height) diff --git a/include/libav.pxd b/include/libav.pxd index 791ffef..2fb8466 100644 --- a/include/libav.pxd +++ b/include/libav.pxd @@ -6,8 +6,6 @@ cdef extern from "pyav/config.h" nogil: char* PYAV_VERSION_STR char* PYAV_COMMIT_STR - int PYAV_HAVE_AVCODEC_SEND_PACKET - include "libavutil/avutil.pxd" include "libavutil/channel_layout.pxd" diff --git a/include/libavcodec/avcodec.pxd b/include/libavcodec/avcodec.pxd index f452e4a..1a421a8 100644 --- a/include/libavcodec/avcodec.pxd +++ b/include/libavcodec/avcodec.pxd @@ -272,20 +272,6 @@ cdef extern from "libavcodec/avcodec.pyav.h" nogil: void (*destruct)(AVPacket*) - cdef int avcodec_decode_video2( - AVCodecContext *ctx, - AVFrame *frame, - int *got_frame, - AVPacket *packet, - ) - - cdef int avcodec_decode_audio4( - AVCodecContext *ctx, - AVFrame *frame, - int *got_frame, - AVPacket *packet, - ) - cdef int avcodec_fill_audio_frame( AVFrame *frame, int nb_channels, diff --git a/include/libavcodec/avcodec.pyav.h b/include/libavcodec/avcodec.pyav.h index 7f6d493..a4aca92 100644 --- a/include/libavcodec/avcodec.pyav.h +++ b/include/libavcodec/avcodec.pyav.h @@ -9,17 +9,6 @@ #endif -#if !PYAV_HAVE_AVCODEC_SEND_PACKET - - // Stub these out so that we don't fail to compile. - int avcodec_send_packet(AVCodecContext *avctx, AVPacket *packet) { return 0; } - int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame) { return 0; } - int avcodec_send_frame(AVCodecContext *avctx, AVFrame *frame) { return 0; } - int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt) { return 0; } - -#endif - - // Some of these properties don't exist in both FFMpeg and LibAV, so we // signal to our code that they are missing via 0. #ifndef AV_CODEC_PROP_INTRA_ONLY diff --git a/setup.py b/setup.py index 513c1d9..6352f07 100644 --- a/setup.py +++ b/setup.py @@ -550,7 +550,6 @@ class ReflectCommand(Command): 'av_calloc', 'avformat_alloc_output_context2', 'avformat_close_input', - 'avcodec_send_packet', ): print("looking for %s... " % func_name, end='\n' if self.debug else '') diff --git a/tests/test_subtitles.py b/tests/test_subtitles.py index 9e5dfce..f28bcad 100644 --- a/tests/test_subtitles.py +++ b/tests/test_subtitles.py @@ -14,10 +14,7 @@ class TestSubtitle(TestCase): fh = av.open(path) subs = [] for packet in fh.demux(): - try: - subs.extend(packet.decode()) - except ValueError: - raise SkipTest + subs.extend(packet.decode()) self.assertEqual(len(subs), 3) self.assertIsInstance(subs[0][0], AssSubtitle) @@ -33,10 +30,7 @@ class TestSubtitle(TestCase): fh = av.open(path) subs = [] for packet in fh.demux(): - try: - subs.extend(packet.decode()) - except ValueError: - raise SkipTest + subs.extend(packet.decode()) self.assertEqual(len(subs), 43) -- GitLab From a0c51ab3c22a46fbb95363b55ce86ee3ed4b3c89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jeremy=20Lain=C3=A9?= <jeremy.laine@m4x.org> Date: Thu, 4 Oct 2018 15:17:28 +0200 Subject: [PATCH 3/3] Remove refcounted_frames It is only used by the avcodec_decode_(audio4|video2) compatibility wrappers, which we don't use. --- av/codec/context.pyx | 3 --- include/libavcodec/avcodec.pxd | 1 - scratchpad/memleak.py | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/av/codec/context.pyx b/av/codec/context.pyx index 25c8f92..e9f1399 100644 --- a/av/codec/context.pyx +++ b/av/codec/context.pyx @@ -85,9 +85,6 @@ cdef class CodecContext(object): raise RuntimeError('Wrapping CodecContext with mismatched codec.') self.codec = wrap_codec(codec if codec != NULL else self.ptr.codec) - # Signal that we want to reference count. - self.ptr.refcounted_frames = 1 - # Set reasonable threading defaults. # count == 0 -> use as many threads as there are CPUs. # type == 2 -> thread within a frame. This does not change the API. diff --git a/include/libavcodec/avcodec.pxd b/include/libavcodec/avcodec.pxd index 1a421a8..36e900a 100644 --- a/include/libavcodec/avcodec.pxd +++ b/include/libavcodec/avcodec.pxd @@ -121,7 +121,6 @@ cdef extern from "libavcodec/avcodec.pyav.h" nogil: int flags int thread_count int thread_type - int refcounted_frames int profile AVDiscard skip_frame diff --git a/scratchpad/memleak.py b/scratchpad/memleak.py index ffab979..20da5d4 100644 --- a/scratchpad/memleak.py +++ b/scratchpad/memleak.py @@ -67,7 +67,7 @@ def transcode_level1_to_level3(): def decode_using_pyav(): print('Decoding using PyAV.') - fh = av.open('ffv1_level3.nut', 'r', options={'refcounted_frames': '1'}) + fh = av.open('ffv1_level3.nut', 'r') for s in fh.streams: #print s, s.thread_type, s.thread_count #pass -- GitLab