Repository: aiortc/aiortc

Issue:
Invalid data found when processing input: 'avcodec_send_packet()' on VP8 track
Hello,

I encountered an issue when subscribing to a VP8 video track. When I call the `recv()` method on the `RemoteStreamTrack` within an `asyncio.Task`, it throws the following error:

```
Exception in thread video-decoder:
Traceback (most recent call last):
  File "/usr/lib/python3.12/threading.py", line 1073, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.12/threading.py", line 1010, in run
    self._target(*self._args, **self._kwargs)
  File "/home/<redacted>/venv/lib/python3.12/site-packages/aiortc/rtcrtpreceiver.py", line 68, in decoder_worker
    for frame in decoder.decode(encoded_frame):
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/<redacted>/venv/lib/python3.12/site-packages/aiortc/codecs/vpx.py", line 162, in decode
    return cast(list[Frame], self.codec.decode(packet))
                             ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "av/codec/context.pyx", line 433, in av.codec.context.CodecContext.decode
  File "av/codec/context.pyx", line 448, in av.codec.context.CodecContext.decode
  File "av/codec/context.pyx", line 340, in av.codec.context.CodecContext._send_packet_and_recv
  File "av/error.pyx", line 424, in av.error.err_check
av.error.InvalidDataError: [Errno 1094995529] Invalid data found when processing input: 'avcodec_send_packet()'
```

After this error, the `video-decoder` thread stops completely. I was wondering what could cause this issue, and whether a `try...except` block could be added to the `decoder_worker` function in aiortc to handle this case gracefully?

Thanks in advance for your help.

Hints from issue comments:
@jlaine: Before we blindly ignore this error, could you try to narrow down what causes this to happen? At the very least I'd like to capture a realistic unit test for this.@apz-noan: For context, the subscribed stream come from Janus (SFU). I instrument a bit of the code to display further information about this exception:
```
processing track <aiortc.rtcrtpreceiver.RemoteStreamTrack object at 0x769b3a1d2450>
[packet] > 0 | False | False >> av.Packet of #0, dts=None, pts=0; 1861 bytes at 0x769b3a1c5f80>
0 | b'\xd1(\x00\xdf\xa7\xb7\xdeH0\xbc\x80\xbe\x81>\x81\x7f\xa0)\x00b+\x82\t\xcaz%\xeb\xb4!\xa5\xc2\xa6\xfc\xef<W\x0cj\x8f\x9c\xa3\xb1\x1cC.\xc8\rw\x14\xfcL\x9d/\x7fh9\x05\x86\x02\x0e\xb0\xb5C\xb3\t\x12\x9f\xdf)C\xa1F\x86\x19\x9a4T\xaf\x92\x9fF\x0b\xcaM<a\xbb\x0f\xf6"\x98~\xb8y\xa1\xb5B1\xbaAn\xd16\x18>n\'\xae\xa4\xc9\xee;\xcf(S\x85\x93\x80\x86\xddN\x8aB\n\\\xa2\x0c6\x95\xdf\xbc\x18\xae\xbf\xd0~l\xe4\x1a`\xff\x12m\xa6\x0ce\xbeQ\xea\x06@L\x04\xfd\x0c\\\rJ\xe7CJ\x8c\xf4O\xec\x01\x08B\xca\xf8kw\xf1_\x93k\xcd1\xe8\x1f\xfa\x9f\xe1\x94\xd9\x19\x00,\xc7\xbf \x0c<\xb0\xd9$\x11\xec\xa2\x7fv\xaa\x05\x88OL\x05\x02\x84ec\x9b*\xb07\xbf\x1crid\x00+\xd2L.\x96\x07\xdd\x9e\xdeihUa\x00\xff\x82\xa1\x92\xe3^$\xb3@F\xfd\x89\xad\xba\xfaJa\xdaJ\xe7\xf7\x86\xe4^\xc7\xc0\x9e$\xc0/>\xbc\xb9*\x0c\xe5\xcc\xe3"Q-\x1c\xb6b\xdd\x84\xe9}Z?h\xb2#\xe5\x06\xb7\xd2\x81\xab2XJ\xc8F\xb6\xbb\xda\x85\xc8$!C\xb1\x1d\xd4\xa7P1D\xcd]\xaa\xdfH\x16\x84\x80\x03\xfae\x93\xb4\xa5\x00i\x0c\xb0R\xb5~(M\xa5e!1\xf9\xa0\x88S\xacJq\x13\xf5QFT\xb4j\x93\x16\xfe\x97-4\xcd,\xc2$l\x1f\xa5\xff\xb3j{\xa1Z\xfak\x1b\xef\x08T\x9f\xc9\xc5rRo1+\xa1\xc4\x13r y\xfdd\x03\x0c!9\x94d\xef\xf33\xb0\xd6\x1b\xdc\xe1\xcer^\x835\n\x1b\x1a\xc7\xde-)\xf5(#\xf4\xf0!N\x99\xc3\xa5\xf1&\x05s\x19;\x1b\xd1";pq\x87\xa7=,\xa4\xe7u\xd4\x93<_\xf2wWT\xc1\xb3=m\xb9\n\xf5\xf6\xf36\xf2&\xcf\x8f\xe1=\xee"yp\xa8#\x04\xd4\x0cf\xe6\x92\x90O\x99\xd9\xd5r\xb0\xef\xfaLS\xf1\xc4\x97\xc7\xb9s\x81\xd8\xa0\xbf\xf5\xfds1\x86\x1d\xe9\xd2\xa3${\x16G]\x17.k\xdefR\x04\x05\x9f\xb1y$\x13\xf8\x14Uk\x1b\x89!\x9e\xbb\xf9\x86\xae\xbdp\x80N\x1b5\xf8lmx\x0e\xc5<\x01\xbe:1\xea{\n\xca\x03m\xf1\xf7`\x82\xd8\x02\x91\xe0\xecQL7\xb1*\x16^\x98\xab\xcf\xfc\xe0Y\xc0\\\xcf?\xc8\xa7\xf2\xd9\x07\x92-\xa1\xe1\x98\x0f\x8a+Qkh\xe0w^\xfb\x1a\xe9\x7f\x89\x8e\xd6\x8dB\x08\x12\xba\xdc\x02\x1a\x85E\x05\xcd\x97\xd2\xe6q\x9e\xa5\xd6\xf1s\xa1\xcf\xef\xd3\xac\xa9A\xa8I\x89z\xf2\x10+\x88\x89\xac`\x96Tu\x0e\x11pm\xa4v\xe5\xcb\xa42\xd1\x15v@\xbc\xcf~\x89\xe82\x8c\xa8\xd1\x12R=\xc1\xa9dG\x08\xf9b-N\xee\xf9\xaf\xfeo+J\xbfV\x7f\xdb\x0f\xabx\xee\xe0G\x02U\x86\xc9\x08\x8eQJ\x17\xe4\xe5\xc9\xe9\xdbX\x0c>xV\x9a\x89 \xe0\x17\xa3\xc9#\xad\xb8<\xda\xb7\xfb\x87\x00\x1a\xc1\xfa\xd0\xc8\xf6\x19\xb7\xd5\xe7\x8f.\xf65\xf3\x96\x0e\xa8\x13\x18\x02:&X\x9a\x8aF\xac\xdb\x12\xf5\xb0\xaf\xc6K9\xe8\r6L\x0e\x7f\xfdZ\xd1\xb9\xff\xda\xe3\xc4\xcf\xceTez\xc2\xc2$S\x08\xd8)\xa1\xd5\xde\r%*\x98\xf2\xd6\x92\xb3X-\xc1E>Rh \xa5UF\xfd\xd3\x94\xfb7\xde\xd8FN\x1a\x17\xd5fWm+\x07\xb6\t\xe3\xf0|Z]A\xc0\xbdH\xe9\xa8t\x83I\xb1\x9e\x96\x8e\xe8\x1b\xa1\xa7\xa7*~0F\x8d\x02\xec\xd1(@\xcf\x9d\x04G\xec\x95\xce\\\xac.\x14\xa9y\xafr\xd6\xec\x9f\r\xd8\xf1\x1ah{\xfe\x81W\x97s!\xc0\x12m\xc1\xf9V\x8fQ\x87\x088\xa3\xa7Q"M\x84\x94@\x02\xcfg\x1f\xafY\xb9\x06\xa6\xcfs\x0bn\xbd\xa5\x82\xfc\xe46\x1b\xa8"\xfc\xac\xdeo\xe8B\xdc\xb9\xca{\x15\x18#\x1a\x08\xcdAA)r\xf9Q\xfdM\xe4\x8c\xcb\xd3\xf8\xfc-Mk\xe4>\xf2\xf4\xea\xd1d\x14\x04\x04%\x07\x80\xfd%S1\x9f\xbb{\x97{Tj\x86\x8b\xafM,\xc9\xdc\x12\x138M2\'\x8f\xfd;q\x83\xf0}\x10\xf6\x01Z\xfcy\x86k\x8b\xc2\xa3B8\xecU\xea\x8d2}\r\x03\xc6\t\x99\x8c\xaf\x81\xde\xe0\xa9\xe9\xb8\xc1\x92!"\n)n\xac9x"0\xab\xe9\xe0\xea\x02\x1c8\x95\xaeAH\xfe\xc1:V\xbf\xe0\x95\xb9oA\xaa\x91\xe1\xa5\x89\xf0\xae\x84iuV\xae\xc2\xb4\x00N\x12\xe4.XA^\\\xbe\xb6-\x89N\xfc\xf76\x93/\x0fs\xd3\xcf\xbc\xc8\x0e\xf0\xec\x91\xc4_\'8\xcd\xbeW\xe5\xcad3\xf3\x9f\xb2\x86\xbd\xecxu\xce\xb4\xdc\x05=\x93\x88\xe6\xech\xcbpo\xf4Q:\xbf5+\xd1\x90\xed\xb9Y\xaa\xa4\xd9\xca\xffb\xb4\x06\x0f9\x0f\xc3\xbd\xb1\x0f#}\xaa\xa0\x9e\xa70&\xfd\xdc\x88\xdb\xbd\x10(:(Q\xeb\xa1a)\xcb\x07/\xc2MY\x1e\x1cG`\x07/\xe6\xfcLQ\x0c&\xfdK\xb8\x1c\x9d\xf7\x13S\xcb\x14f\x8b\x8d\xb1\x14q\xc9\x91\xb0Zj8\x9b\x06Hh*\xef\xfe\xf7\xc7\x90\xa2\xaa\t\xd9\x8b\xe6Y\x1d\xcd\x10\xef\x04-\xa1\xfe\x1b.\xbc\x94\xd7A\t@\x88\xa6>3\x83\xf3$\xef\x06z\x13\xa8\x98O\xdcPW\xe0\xd5\x8b\xf1\xb6\xba\xb3\xdb\x14\x08x\x19z\xae\xdb\x8cq\xea\x8c\x89\x19\xadm\xf9\x80|\x17\x1efb\x0c\xaaT\x1e\xae\xe8\xcc\x1e\x15\x9c\x14\x84\xc3\xc7c\x90b\x8f\xd9\x8cN\xa6.\xc8|\xb8Map\x06\xf7s{{}\x89\xd4\x08\xa6\x96\x99\xb9\x06m\x18\xbe\xa7^\xdf\x08\xdd\xa9\xfe\x1e+\xba\x01v\x9d\x10\x8df{\x88)*\x88\x151\xe6JZxO)\x99\xe5R(o\x11LI\xea5\xab\xdd\x82\'\rg\xf0V\xb3\xe5\x16:\x1cL\x8a\xf7\rO\x0f\x9e\nd\xd9\xd5\x86\xe3nHXb\x8e\x99\'\x19\xaa\xa1\x97\x80\xbe\x01\xb0-\xc3\xa6\x13\xd7\xe5\xb7\xa3_\x02\x9e2\xd5\x9b\xc42\xfa\x7f\xe8\x15\xc5\x969V\xf9\xe5\x08\xbc\x86\t\xd7l\xf2\xd4\x86)(\x95\xc5\x9f\xb0\xa8EkuN{u\x1d\x1e<\xc2\xc8\x17-\xfb>\x9e\xe7Q\xa1|B3]\xd9\xb9x\xdd\xab\xed|)\x17wn\xf4\x9an\xb0[\x98\xfe\x00Lf\x0b\xe7\xfb\xcbt\x1a\xe5jO\xae\xe1\xc95\x03\xd5-Y\x05\xa9\xe9\xf3&V\\\x1e\xbf\x0c/\xa5]>im\x11\xf7\r\xea\'P>v\xa8\x06\xc2G\xdb\x02z\xed\xfe\xd3\xc0`\xfc\xb7\xf1\xdcD\xd89.\xa4\xf9\xde\xdc\xdb\n\x08\x92E\xb1>\xf9\x1b\xaa\xda\xcd\x1e\xa0/g\xf4g,b\xf3\x17\xdd\x1c6\xabG\xe1\xe8\xd8\xf5\x07\xfbJ_D\xff R\xaf\xde\x1a\xb4Da\x8a\x98\x9949)*\xd8M)\x8b\xc6=\xd9Z[\xbfA\xc0\x85\xe2\x14\xa7,\xfe-\\-,\xb0d\xe8\xec$K\x80&\xe1\xaa\xca\xcb5`\xaa\xf3]b\xe3\n\xb6\x8aGF\x08\xf8\x04]"\x9d)\x99kT\x8e\xc1\xec\x17\xca\x97\xe6@\xf3\x06\xc7\xe3x\xb9v\xd5\xbbs\x1d\xcf\xda\x1f\xea{\xcf\x02\xc3\x8cDS\xbb\xa3E\xdd)?m\x04\x0e\n\tl\xb5\x81\xda\xe54\xff\x84\xe8\xc3MAVL\xf7\x9a\xce\x878|\x187\xcdC#|7\xcd\xbb\x9fSe\x87\xd8\xc0/\x99\x9b\xc7\xed\xc7}|t\xd4\x1c:\xc5\x81\xf9\xf3\xac\x9c\xb5\xe5\xa2\\y\x17\xd4\xa2\x9cxF\xa3s\x8eS\xc9\xf7\xec-\x86(\xd98O]\xb0\xcb\x98\xdc\xccF\xc4\xe7(\xffcZ\x15\xd0'
Exception in thread video-decoder:
Traceback (most recent call last):
  File "/usr/lib/python3.12/threading.py", line 1073, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.12/threading.py", line 1010, in run
    self._target(*self._args, **self._kwargs)
  File "/home/<redacted>venv/lib/python3.12/site-packages/aiortc/rtcrtpreceiver.py", line 75, in decoder_worker
    raise err
  File "/home/<redacted>/venv/lib/python3.12/site-packages/aiortc/rtcrtpreceiver.py", line 70, in decoder_worker
    for frame in decoder.decode(encoded_frame):
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/<redacted>/venv/lib/python3.12/site-packages/aiortc/codecs/vpx.py", line 163, in decode
    return cast(list[Frame], self.codec.decode(packet))
                             ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "av/codec/context.pyx", line 433, in av.codec.context.CodecContext.decode
  File "av/codec/context.pyx", line 448, in av.codec.context.CodecContext.decode
  File "av/codec/context.pyx", line 340, in av.codec.context.CodecContext._send_packet_and_recv
  File "av/error.pyx", line 424, in av.error.err_check
av.error.InvalidDataError: [Errno 1094995529] Invalid data found when processing input: 'avcodec_send_packet()'
```

Also the error seems to appear randomly (maybe a keyframe is not received as first packet?), sometimes it works flawlessly.@jlaine: Hm looking into it, our previous code did silently discard errors:

```python
        result = lib.vpx_codec_decode(
            self.codec,
            encoded_frame.data,
            len(encoded_frame.data),
            ffi.NULL,
            lib.VPX_DL_REALTIME,
        )
        if result == lib.VPX_CODEC_OK:
            ...
``

Solution patch:
diff --git a/src/aiortc/codecs/vpx.py b/src/aiortc/codecs/vpx.py
index 7232d47a..0e8c2a05 100644
--- a/src/aiortc/codecs/vpx.py
+++ b/src/aiortc/codecs/vpx.py
@@ -1,3 +1,4 @@
+import logging
 import multiprocessing
 import random
 from struct import pack, unpack_from
@@ -13,6 +14,8 @@
 from ..mediastreams import VIDEO_TIME_BASE, convert_timebase
 from .base import Decoder, Encoder
 
+logger = logging.getLogger(__name__)
+
 DEFAULT_BITRATE = 500000  # 500 kbps
 MIN_BITRATE = 250000  # 250 kbps
 MAX_BITRATE = 1500000  # 1.5 Mbps
@@ -168,10 +171,14 @@ def __init__(self) -> None:
         self.codec = CodecContext.create("libvpx", "r")
 
     def decode(self, encoded_frame: JitterFrame) -> list[Frame]:
-        packet = Packet(encoded_frame.data)
-        packet.pts = encoded_frame.timestamp
-        packet.time_base = VIDEO_TIME_BASE
-        return cast(list[Frame], self.codec.decode(packet))
+        try:
+            packet = Packet(encoded_frame.data)
+            packet.pts = encoded_frame.timestamp
+            packet.time_base = VIDEO_TIME_BASE
+            return cast(list[Frame], self.codec.decode(packet))
+        except av.FFmpegError as e:
+            logger.warning("Vp8Decoder() failed to decode, skipping package: " + str(e))
+            return []
 
 
 class Vp8Encoder(Encoder):


Test patch:
diff --git a/tests/test_vpx.py b/tests/test_vpx.py
index d80aff16..0da19734 100644
--- a/tests/test_vpx.py
+++ b/tests/test_vpx.py
@@ -1,4 +1,6 @@
 import fractions
+import io
+from contextlib import redirect_stderr
 from unittest import TestCase
 
 from aiortc.codecs import get_decoder, get_encoder
@@ -8,6 +10,7 @@
     VpxPayloadDescriptor,
     number_of_threads,
 )
+from aiortc.jitterbuffer import JitterFrame
 from aiortc.rtcrtpparameters import RTCRtpCodecParameters
 
 from .codecs import CodecTestCase
@@ -161,6 +164,11 @@ def test_decoder(self) -> None:
         decoder = get_decoder(VP8_CODEC)
         self.assertIsInstance(decoder, Vp8Decoder)
 
+        # decode junk
+        with redirect_stderr(io.StringIO()):
+            frames = decoder.decode(JitterFrame(data=b"123", timestamp=0))
+        self.assertEqual(frames, [])
+
     def test_encoder(self) -> None:
         encoder = self.ensureIsInstance(get_encoder(VP8_CODEC), Vp8Encoder)