Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bitstream filter API fixes #1379

Merged
merged 2 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions av/bitstream.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ cdef class BitStreamFilterContext:
cdef const lib.AVBSFContext *ptr

cpdef filter(self, Packet packet=?)
cpdef flush(self)
6 changes: 5 additions & 1 deletion av/bitstream.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@ from .stream import Stream

class BitStreamFilterContext:
def __init__(
self, filter_description: str | bytes, stream: Stream | None = None
self,
filter_description: str | bytes,
in_stream: Stream | None = None,
out_stream: Stream | None = None,
): ...
def filter(self, packet: Packet | None) -> list[Packet]: ...
def flush(self) -> None: ...

bitstream_filters_available: set[str]
25 changes: 19 additions & 6 deletions av/bitstream.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,33 @@ cdef class BitStreamFilterContext:
Initializes a bitstream filter: a way to directly modify packet data.

Wraps :ffmpeg:`AVBSFContext`

:param Stream in_stream: A stream that defines the input codec for the bitfilter.
:param Stream out_stream: A stream whose codec is overwritten using the output parameters from the bitfilter.
"""
def __cinit__(self, filter_description, Stream stream=None):
def __cinit__(self, filter_description, Stream in_stream=None, Stream out_stream=None):
cdef int res
cdef char *filter_str = filter_description

with nogil:
res = lib.av_bsf_list_parse_str(filter_str, &self.ptr)
err_check(res)

if stream is not None:
with nogil:
res = lib.avcodec_parameters_copy(self.ptr.par_in, stream.ptr.codecpar)
err_check(res)
if in_stream is not None:
with nogil:
res = lib.avcodec_parameters_copy(self.ptr.par_out, stream.ptr.codecpar)
res = lib.avcodec_parameters_copy(self.ptr.par_in, in_stream.ptr.codecpar)
err_check(res)

with nogil:
res = lib.av_bsf_init(self.ptr)
err_check(res)

if out_stream is not None:
with nogil:
res = lib.avcodec_parameters_copy(out_stream.ptr.codecpar, self.ptr.par_out)
err_check(res)
lib.avcodec_parameters_to_context(out_stream.codec_context.ptr, out_stream.ptr.codecpar)

def __dealloc__(self):
if self.ptr:
lib.av_bsf_free(&self.ptr)
Expand Down Expand Up @@ -65,6 +71,13 @@ cdef class BitStreamFilterContext:

output.append(new_packet)

cpdef flush(self):
"""
Reset the internal state of the filter.
Should be called e.g. when seeking.
Can be used to make the filter usable again after draining it with EOF marker packet.
"""
lib.av_bsf_flush(self.ptr)

cdef get_filter_names():
names = set()
Expand Down
4 changes: 4 additions & 0 deletions include/libavcodec/bsf.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,7 @@ cdef extern from "libavcodec/bsf.h" nogil:
AVBSFContext *ctx,
AVPacket *pkt
)

cdef void av_bsf_flush(
AVBSFContext *ctx
)
44 changes: 40 additions & 4 deletions tests/test_bitstream.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
from .common import TestCase, fate_suite


def is_annexb(packet: Packet) -> bool:
data = bytes(packet)
return data[:3] == b"\0\0\x01" or data[:4] == b"\0\0\0\x01"


class TestBitStreamFilters(TestCase):
def test_filters_availible(self) -> None:
self.assertIn("h264_mp4toannexb", bitstream_filters_available)
Expand Down Expand Up @@ -43,10 +48,6 @@ def test_filter_setts(self) -> None:
self.assertEqual(result_packets[1].pts, 1)

def test_filter_h264_mp4toannexb(self) -> None:
def is_annexb(packet: Packet) -> bool:
data = bytes(packet)
return data[:3] == b"\0\0\x01" or data[:4] == b"\0\0\0\x01"

with av.open(fate_suite("h264/interlaced_crop.mp4"), "r") as container:
stream = container.streams.video[0]
ctx = BitStreamFilterContext("h264_mp4toannexb", stream)
Expand All @@ -60,3 +61,38 @@ def is_annexb(packet: Packet) -> bool:

for p in res_packets:
self.assertTrue(is_annexb(p))

def test_filter_output_parameters(self) -> None:
with av.open(fate_suite("h264/interlaced_crop.mp4"), "r") as container:
stream = container.streams.video[0]

self.assertFalse(is_annexb(stream.codec_context.extradata))
ctx = BitStreamFilterContext("h264_mp4toannexb", stream)
self.assertFalse(is_annexb(stream.codec_context.extradata))
del ctx

_ = BitStreamFilterContext("h264_mp4toannexb", stream, out_stream=stream)
self.assertTrue(is_annexb(stream.codec_context.extradata))

def test_filter_flush(self) -> None:
with av.open(fate_suite("h264/interlaced_crop.mp4"), "r") as container:
stream = container.streams.video[0]
ctx = BitStreamFilterContext("h264_mp4toannexb", stream)

res_packets = []
for p in container.demux(stream):
res_packets.extend(ctx.filter(p))
self.assertEqual(len(res_packets), stream.frames)

container.seek(0)
# Without flushing, we expect to get an error: "A non-NULL packet sent after an EOF."
with self.assertRaises(ValueError):
for p in container.demux(stream):
ctx.filter(p)

ctx.flush()
container.seek(0)
for p in container.demux(stream):
res_packets.extend(ctx.filter(p))

self.assertEqual(len(res_packets), stream.frames * 2)
Loading