22
33import cython
44from cython .cimports import libav as lib
5+ from cython .cimports .av .bitstream import BitStreamFilterContext
56from cython .cimports .av .codec .codec import Codec
67from cython .cimports .av .codec .context import CodecContext , wrap_codec_context
78from cython .cimports .av .container .streams import StreamContainer
1011from cython .cimports .av .packet import Packet
1112from cython .cimports .av .stream import Stream , wrap_stream
1213from cython .cimports .av .utils import dict_to_avdict , to_avrational
14+ from cython .cimports .libc .stdint import uint8_t
15+ from cython .cimports .libc .string import memcpy , memset
16+
17+
18+ @cython .cfunc
19+ def _set_codecpar_extradata (
20+ stream : cython .pointer [lib .AVStream ],
21+ data : cython .pointer [uint8_t ],
22+ size : cython .int ,
23+ ):
24+ buf : cython .p_uchar = cython .cast (
25+ cython .p_uchar , lib .av_malloc (size + lib .AV_INPUT_BUFFER_PADDING_SIZE )
26+ )
27+ if buf == cython .NULL :
28+ raise MemoryError ("Could not allocate extradata" )
29+
30+ memcpy (buf , data , size )
31+ memset (buf + size , 0 , lib .AV_INPUT_BUFFER_PADDING_SIZE )
32+
33+ lib .av_freep (cython .address (stream .codecpar .extradata ))
34+ stream .codecpar .extradata = buf
35+ stream .codecpar .extradata_size = size
1336
1437
1538@cython .cfunc
1639def close_output (self : OutputContainer ):
40+ if self .packet_ptr != cython .NULL and self ._buffered_packets :
41+ buffered : list = self ._buffered_packets
42+ self ._buffered_packets = []
43+ packet : Packet
44+ for packet in buffered :
45+ self ._mux_one (packet )
46+
1747 self .streams = StreamContainer ()
1848 if self ._myflag & 12 == 4 : # enum.started and not enum.done
1949 # If the underlying Python IO file was already closed (e.g. during GC
@@ -38,6 +68,8 @@ class OutputContainer(Container):
3868 def __cinit__ (self , * args , ** kwargs ):
3969 self .streams = StreamContainer ()
4070 self .metadata = {}
71+ self ._extradata_bsfs = {}
72+ self ._buffered_packets = []
4173 with cython .nogil :
4274 self .packet_ptr = lib .av_packet_alloc ()
4375
@@ -558,6 +590,13 @@ def mux(self, packets):
558590 self .mux_one (packet )
559591
560592 def mux_one (self , packet : Packet ):
593+ if not (self ._myflag & 4 ) and self ._buffer_for_extradata (packet ):
594+ return
595+
596+ self ._mux_one (packet )
597+
598+ @cython .cfunc
599+ def _mux_one (self , packet : Packet ):
561600 self .start_encoding ()
562601
563602 # Assert the packet is in stream time.
@@ -577,3 +616,86 @@ def mux_one(self, packet: Packet):
577616 with cython .nogil :
578617 ret : cython .int = lib .av_interleaved_write_frame (self .ptr , self .packet_ptr )
579618 self .err_check (ret )
619+
620+ @cython .cfunc
621+ def _buffer_for_extradata (self , packet : Packet ):
622+ """Buffer ``packet`` until extradata is known for all mux streams that
623+ need it. Returns True if the packet was buffered (caller should stop)."""
624+ if not (self ._myflag & 16 ): # extradata_planned
625+ self ._myflag |= 16
626+ if self .ptr .oformat .flags & lib .AVFMT_GLOBALHEADER :
627+ stream : Stream
628+ for stream in self .streams :
629+ if (
630+ stream .codec_context is not None
631+ or stream .ptr .codecpar .extradata != cython .NULL
632+ ):
633+ continue
634+ try :
635+ bsf = BitStreamFilterContext (
636+ "extract_extradata" , in_stream = stream
637+ )
638+ except Exception :
639+ continue # Codec does not support extradata extraction.
640+ self ._extradata_bsfs [stream .ptr .index ] = bsf
641+
642+ if not self ._extradata_bsfs :
643+ return False # Nothing to wait for; mux normally.
644+
645+ self ._try_extract_extradata (packet )
646+ self ._buffered_packets .append (packet )
647+ if self ._extradata_bsfs :
648+ return True # Still waiting on some stream's extradata.
649+
650+ # All extradata is resolved: write the header and flush buffered packets.
651+ buffered : list = self ._buffered_packets
652+ self ._buffered_packets = []
653+ buffered_packet : Packet
654+ for buffered_packet in buffered :
655+ self ._mux_one (buffered_packet )
656+ return True
657+
658+ @cython .cfunc
659+ def _try_extract_extradata (self , packet : Packet ):
660+ idx : cython .int = packet .ptr .stream_index
661+ if idx not in self ._extradata_bsfs :
662+ return
663+
664+ bsf_wrapper : BitStreamFilterContext = self ._extradata_bsfs [idx ]
665+ bsf : cython .pointer [lib .AVBSFContext ] = bsf_wrapper .ptr
666+
667+ tmp : cython .pointer [lib .AVPacket ] = lib .av_packet_alloc ()
668+ if tmp == cython .NULL :
669+ raise MemoryError ("Could not allocate packet" )
670+
671+ size : cython .size_t = 0
672+ sd : cython .pointer [uint8_t ]
673+ try :
674+ # Clone the packet so the filter does not consume the caller's data.
675+ if lib .av_packet_ref (tmp , packet .ptr ) < 0 :
676+ return
677+
678+ if lib .av_bsf_send_packet (bsf , tmp ) < 0 :
679+ lib .av_packet_unref (tmp ) # send failed; we still own the ref
680+ return
681+
682+ # The filter rejects packets that are already length-prefixed rather
683+ # than annex-b, returning an error here; treat that and EOF/EAGAIN
684+ # alike as "no in-band extradata".
685+ while lib .av_bsf_receive_packet (bsf , tmp ) == 0 :
686+ sd = lib .av_packet_get_side_data (
687+ tmp , lib .AV_PKT_DATA_NEW_EXTRADATA , cython .address (size )
688+ )
689+ if sd != cython .NULL and size > 0 :
690+ _set_codecpar_extradata (
691+ self .ptr .streams [idx ], sd , cython .cast (cython .int , size )
692+ )
693+ lib .av_packet_unref (tmp )
694+ break
695+ lib .av_packet_unref (tmp )
696+ finally :
697+ lib .av_packet_free (cython .address (tmp ))
698+ # A stream's first packet is the only reliable place to find in-band
699+ # parameter sets, so stop waiting on this stream regardless of the
700+ # result, falling back to the muxer's default behavior.
701+ del self ._extradata_bsfs [idx ]
0 commit comments