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