مشكلة لفك تشفير الفيديو H264 عبر RTP مع FFMPEG (libavcodec)

StackOverflow https://stackoverflow.com/questions/3493742

  •  28-09-2019
  •  | 
  •  

سؤال

لقد قمت بتعيين ملف تعريف _idc ، level_idc ، extradata et extredata_size من avcodeccontext مع مجموعة et sprop-propeter على مستوى الملف الشخصي من SDP.

أفصل فك تشفير الشريحة المشفرة و SPS و PPS و NAL_IDR_SLICE حزمة:

فيه:

uint8_t start_sequence [] = {0 ، 0 ، 1} ؛ int size = recv (id_de_la_socket ، (char*) rtpreceive ، 65535،0) ؛

شريحة مشفرة:

char *z = new char[size-16+sizeof(start_sequence)];
    memcpy(z,&start_sequence,sizeof(start_sequence));
    memcpy(z+sizeof(start_sequence),rtpReceive+16,size-16);
    ConsumedBytes = avcodec_decode_video(codecContext,pFrame,&GotPicture,(uint8_t*)z,size-16+sizeof(start_sequence));
    delete z;

النتيجة: المستهلكين> 0 و gotpicture> 0 (في كثير من الأحيان)

SPS و PPS:

رمز متطابق. النتيجة: الاستهلاك> 0 و gotpicture = 0

أعتقد أنه من الطبيعي

عندما أجد زوجين جديدين SPS/PPS ، أقوم بتحديث extradata و extrada_size مع حمولات هذه الحزمة وحجمها.

nal_idr_slice:

نوع وحدة NAL هو 28 => إطار IDR مجزأ من ذلك ، لقد حاولت طريقة لفك التشفير

1) أنا بادئة الجزء الأول (بدون رأس RTP) مع التسلسل 0x000001 وأرسله إلى Avcodec_Decode_Video. ثم أرسل بقية الشظايا إلى هذه الوظيفة.

2) أنا بادئة الشظية الأولى (بدون رأس RTP) مع التسلسل 0x000001 وأسلوب بقية الشظايا إليها. أرسل هذا المخزن المؤقت إلى وحدة فك الترميز.

في كلتا الحالتين ، ليس لدي أي خطأ (مستهلكين> 0) ولكني لا أكتشف أي إطار (getPicture = 0) ...

ما المشكلة ؟

هل كانت مفيدة؟

المحلول

في RTP جميع H264 I-Frames (IDRs) مجزأة. عندما تتلقى RTP ، يجب عليك أولاً تخطي الرأس (المعتاد الأول 12 بايت) ثم الوصول إلى وحدة NAL (بايت الحمولة الأولى). إذا كان NAL هو 28 (1C) ، فهذا يعني أن الحمولة التالية تمثل جزءًا من H264 IDR (I-Frame) وأنك تحتاج إلى جمعها جميعًا لإعادة بناء H264 IDR (I-Frame).

يحدث التفتت بسبب محدودة MTU ، و IDR أكبر بكثير. شظايا واحدة يمكن أن تبدو هكذا:

شظية التي تبدأ بت = 1:

First byte:  [ 3 NAL UNIT BITS | 5 FRAGMENT TYPE BITS] 
Second byte: [ START BIT | END BIT | RESERVED BIT | 5 NAL UNIT BITS] 
Other bytes: [... IDR FRAGMENT DATA...]

شظايا أخرى:

First byte:  [ 3 NAL UNIT BITS | 5 FRAGMENT TYPE BITS]  
Other bytes: [... IDR FRAGMENT DATA...]

لإعادة بناء IDR ، يجب عليك جمع هذه المعلومات:

int fragment_type = Data[0] & 0x1F;
int nal_type = Data[1] & 0x1F;
int start_bit = Data[1] & 0x80;
int end_bit = Data[1] & 0x40;

إذا fragment_type == 28 ثم الحمولة التالية هي جزء واحد من IDR. الشيك التالي هو start_bit تعيين ، إذا كان الأمر كذلك ، فإن هذه الشظية هي الأولى في تسلسل. يمكنك استخدامه لإعادة بناء بايت IDR من خلال أخذ أول 3 بتات من بايت الحمولة الأولى (3 NAL UNIT BITS) ودمجها مع آخر 5 بتات من بايت الحمولة الثانية (5 NAL UNIT BITS) لذلك ستحصل على بايت مثل هذا [3 NAL UNIT BITS | 5 NAL UNIT BITS]. ثم اكتب تلك البايت أولاً في مخزن مؤقت واضح مع جميع البايتات التالية من تلك الشظية. تذكر أن تخطي البايت الأول في تسلسل لأنه ليس جزءًا من IDR ، ولكنه يحدد الجزء فقط.

إذا start_bit و end_bit هي 0 ثم اكتب الحمولة النافعة (تخطي بايت الحمولة الأولى التي تحدد الجزء) إلى المخزن المؤقت.

إذا كان start_bit هو 0 و end_bit هو 1 ، فهذا يعني أنه الجزء الأخير ، وأنت تكتب فقط حمولةها (تخطي البايت الأول الذي يحدد الجزء) إلى المخزن المؤقت ، والآن لديك IDR إعادة بنائها.

إذا كنت بحاجة إلى بعض التعليمات البرمجية ، فقط اسأل في التعليق ، سأقوم بنشره ، لكنني أعتقد أن هذا واضح تمامًا كيفية القيام ... =)

بخصوص فك التشفير

لقد عبرت ذهني اليوم لماذا تحصل على خطأ في فك تشفير IDR (افترضت أنك أعيد بناؤه جيدًا). كيف تقوم ببناء سجل تكوين فك ترميز AVC الخاص بك؟ هل lib التي تستخدمها لديها هذا الآلية؟ إذا لم يكن الأمر كذلك ، ولم تسمع بهذا ، مواصلة القراءة ...

تم تحديد AVCDCR للسماح لدلالات فك التشفير بتحليل جميع البيانات التي يحتاجونها بسرعة لفك تشفير دفق الفيديو H264 (AVC). والبيانات تتبع:

  • profileIDC
  • profileiop
  • LevelIDC
  • SPS (مجموعات المعلمة التسلسل)
  • PPS (مجموعات معلمة الصورة)

يتم إرسال كل هذه البيانات في جلسة RTSP في SDP تحت الحقول: profile-level-id و sprop-parameter-sets.

فك تشفير الملف الشخصي

تنقسم سلسلة معرف المستوى الفائقة إلى 3 أساسية ، كل شخصان طويلان:

[PROFILE IDC][PROFILE IOP][LEVEL IDC]

يمثل كل فرعية بايت واحد في Base16! لذلك ، إذا كان ملف تعريف IDC هو 28 ، فهذا يعني أنه فعلي 40 في BASE10. في وقت لاحق سوف تستخدم قيم BASE10 لإنشاء سجل تكوين وحدة فك ترميز AVC.

فك تشفير مجموعات المعلمة

سبروبس معتاد 2 سلاسل (يمكن أن تكون أكثر) مفصولة الفاصلة ، و BASE64 مشفرة! يمكنك فك تشفير كلاهما ولكن ليست هناك حاجة لذلك. وظيفتك هنا هي فقط لتحويلها من سلسلة BASE64 إلى مجموعة بايت للاستخدام لاحقًا. الآن لديك صفيفان بايتان ، أول صفيف لنا SPS ، والثاني هو PPS.

بناء AVCDCR

الآن ، لديك كل ما تحتاجه لإنشاء AVCDCR ، تبدأ بصنع عازلة نظيفة جديدة ، والآن اكتب هذه الأشياء فيها بالترتيب الموضح هنا:

1 - بايت لها قيمة 1 ويمثل الإصدار

2 - ملف التعريف IDC بايت

3 - بايت IOP الفاصل

4 - المستوى IDC بايت

5 - بايت مع القيمة 0xFF (Google سجل تكوين فك ترميز AVC لمعرفة ما هذا)

6 - بايت مع القيمة 0xe1

7 - قصير مع قيمة طول صفيف SPS

8 - صفيف بايت SPS

9-بايت مع عدد صفائف PPS (يمكن أن يكون لديك المزيد منها في مجموعة براميلات sprop)

10 - قصير مع طول صفيف PPS التالي

11 - صفيف PPS

فك تشفير دفق الفيديو

الآن لديك مجموعة بايت تخبر Decoder كيفية فك تشفير دفق الفيديو H264. أعتقد أنك بحاجة إلى هذا إذا لم يبنيه lib نفسه من SDP ...

نصائح أخرى

لا أعرف عن بقية تطبيقك ، لكن يبدو من المحتمل أن "الشظايا التي تتلقاها هي وحدات NAL. لذلك ، قد يحتاج كل منها إلى رمز بدء NALU (00 00 01 أو 00 00 00 01) إلحاقها عند إعادة بناء Bitstream قبل إرسالها إلى FFMPEG.

على أي حال ، قد تجد RFC لحزم RTP H264 مفيدة:

http://www.rfc-editor.org/rfc/rfc3984.txt

أتمنى أن يساعدك هذا!

لدي تطبيق لهذا @ https://net7mma.codeplex.com/ بالنسبة لـ C# ولكن العملية هي نفسها في كل مكان.

هنا هو الرمز ذي الصلة

/// <summary>
    /// Implements Packetization and Depacketization of packets defined in <see href="https://tools.ietf.org/html/rfc6184">RFC6184</see>.
    /// </summary>
    public class RFC6184Frame : Rtp.RtpFrame
    {
        /// <summary>
        /// Emulation Prevention
        /// </summary>
        static byte[] NalStart = { 0x00, 0x00, 0x01 };

        public RFC6184Frame(byte payloadType) : base(payloadType) { }

        public RFC6184Frame(Rtp.RtpFrame existing) : base(existing) { }

        public RFC6184Frame(RFC6184Frame f) : this((Rtp.RtpFrame)f) { Buffer = f.Buffer; }

        public System.IO.MemoryStream Buffer { get; set; }

        /// <summary>
        /// Creates any <see cref="Rtp.RtpPacket"/>'s required for the given nal
        /// </summary>
        /// <param name="nal">The nal</param>
        /// <param name="mtu">The mtu</param>
        public virtual void Packetize(byte[] nal, int mtu = 1500)
        {
            if (nal == null) return;

            int nalLength = nal.Length;

            int offset = 0;

            if (nalLength >= mtu)
            {
                //Make a Fragment Indicator with start bit
                byte[] FUI = new byte[] { (byte)(1 << 7), 0x00 };

                bool marker = false;

                while (offset < nalLength)
                {
                    //Set the end bit if no more data remains
                    if (offset + mtu > nalLength)
                    {
                        FUI[0] |= (byte)(1 << 6);
                        marker = true;
                    }
                    else if (offset > 0) //For packets other than the start
                    {
                        //No Start, No End
                        FUI[0] = 0;
                    }

                    //Add the packet
                    Add(new Rtp.RtpPacket(2, false, false, marker, PayloadTypeByte, 0, SynchronizationSourceIdentifier, HighestSequenceNumber + 1, 0, FUI.Concat(nal.Skip(offset).Take(mtu)).ToArray()));

                    //Move the offset
                    offset += mtu;
                }
            } //Should check for first byte to be 1 - 23?
            else Add(new Rtp.RtpPacket(2, false, false, true, PayloadTypeByte, 0, SynchronizationSourceIdentifier, HighestSequenceNumber + 1, 0, nal));
        }

        /// <summary>
        /// Creates <see cref="Buffer"/> with a H.264 RBSP from the contained packets
        /// </summary>
        public virtual void Depacketize() { bool sps, pps, sei, slice, idr; Depacketize(out sps, out pps, out sei, out slice, out idr); }

        /// <summary>
        /// Parses all contained packets and writes any contained Nal Units in the RBSP to <see cref="Buffer"/>.
        /// </summary>
        /// <param name="containsSps">Indicates if a Sequence Parameter Set was found</param>
        /// <param name="containsPps">Indicates if a Picture Parameter Set was found</param>
        /// <param name="containsSei">Indicates if Supplementatal Encoder Information was found</param>
        /// <param name="containsSlice">Indicates if a Slice was found</param>
        /// <param name="isIdr">Indicates if a IDR Slice was found</param>
        public virtual void Depacketize(out bool containsSps, out bool containsPps, out bool containsSei, out bool containsSlice, out bool isIdr)
        {
            containsSps = containsPps = containsSei = containsSlice = isIdr = false;

            DisposeBuffer();

            this.Buffer = new MemoryStream();

            //Get all packets in the frame
            foreach (Rtp.RtpPacket packet in m_Packets.Values.Distinct()) 
                ProcessPacket(packet, out containsSps, out containsPps, out containsSei, out containsSlice, out isIdr);

            //Order by DON?
            this.Buffer.Position = 0;
        }

        /// <summary>
        /// Depacketizes a single packet.
        /// </summary>
        /// <param name="packet"></param>
        /// <param name="containsSps"></param>
        /// <param name="containsPps"></param>
        /// <param name="containsSei"></param>
        /// <param name="containsSlice"></param>
        /// <param name="isIdr"></param>
        internal protected virtual void ProcessPacket(Rtp.RtpPacket packet, out bool containsSps, out bool containsPps, out bool containsSei, out bool containsSlice, out bool isIdr)
        {
            containsSps = containsPps = containsSei = containsSlice = isIdr = false;

            //Starting at offset 0
            int offset = 0;

            //Obtain the data of the packet (without source list or padding)
            byte[] packetData = packet.Coefficients.ToArray();

            //Cache the length
            int count = packetData.Length;

            //Must have at least 2 bytes
            if (count <= 2) return;

            //Determine if the forbidden bit is set and the type of nal from the first byte
            byte firstByte = packetData[offset];

            //bool forbiddenZeroBit = ((firstByte & 0x80) >> 7) != 0;

            byte nalUnitType = (byte)(firstByte & Common.Binary.FiveBitMaxValue);

            //o  The F bit MUST be cleared if all F bits of the aggregated NAL units are zero; otherwise, it MUST be set.
            //if (forbiddenZeroBit && nalUnitType <= 23 && nalUnitType > 29) throw new InvalidOperationException("Forbidden Zero Bit is Set.");

            //Determine what to do
            switch (nalUnitType)
            {
                //Reserved - Ignore
                case 0:
                case 30:
                case 31:
                    {
                        return;
                    }
                case 24: //STAP - A
                case 25: //STAP - B
                case 26: //MTAP - 16
                case 27: //MTAP - 24
                    {
                        //Move to Nal Data
                        ++offset;

                        //Todo Determine if need to Order by DON first.
                        //EAT DON for ALL BUT STAP - A
                        if (nalUnitType != 24) offset += 2;

                        //Consume the rest of the data from the packet
                        while (offset < count)
                        {
                            //Determine the nal unit size which does not include the nal header
                            int tmp_nal_size = Common.Binary.Read16(packetData, offset, BitConverter.IsLittleEndian);
                            offset += 2;

                            //If the nal had data then write it
                            if (tmp_nal_size > 0)
                            {
                                //For DOND and TSOFFSET
                                switch (nalUnitType)
                                {
                                    case 25:// MTAP - 16
                                        {
                                            //SKIP DOND and TSOFFSET
                                            offset += 3;
                                            goto default;
                                        }
                                    case 26:// MTAP - 24
                                        {
                                            //SKIP DOND and TSOFFSET
                                            offset += 4;
                                            goto default;
                                        }
                                    default:
                                        {
                                            //Read the nal header but don't move the offset
                                            byte nalHeader = (byte)(packetData[offset] & Common.Binary.FiveBitMaxValue);

                                            if (nalHeader > 5)
                                            {
                                                if (nalHeader == 6)
                                                {
                                                    Buffer.WriteByte(0);
                                                    containsSei = true;
                                                }
                                                else if (nalHeader == 7)
                                                {
                                                    Buffer.WriteByte(0);
                                                    containsPps = true;
                                                }
                                                else if (nalHeader == 8)
                                                {
                                                    Buffer.WriteByte(0);
                                                    containsSps = true;
                                                }
                                            }

                                            if (nalHeader == 1) containsSlice = true;

                                            if (nalHeader == 5) isIdr = true;

                                            //Done reading
                                            break;
                                        }
                                }

                                //Write the start code
                                Buffer.Write(NalStart, 0, 3);

                                //Write the nal header and data
                                Buffer.Write(packetData, offset, tmp_nal_size);

                                //Move the offset past the nal
                                offset += tmp_nal_size;
                            }
                        }

                        return;
                    }
                case 28: //FU - A
                case 29: //FU - B
                    {
                        /*
                         Informative note: When an FU-A occurs in interleaved mode, it
                         always follows an FU-B, which sets its DON.
                         * Informative note: If a transmitter wants to encapsulate a single
                          NAL unit per packet and transmit packets out of their decoding
                          order, STAP-B packet type can be used.
                         */
                        //Need 2 bytes
                        if (count > 2)
                        {
                            //Read the Header
                            byte FUHeader = packetData[++offset];

                            bool Start = ((FUHeader & 0x80) >> 7) > 0;

                            //bool End = ((FUHeader & 0x40) >> 6) > 0;

                            //bool Receiver = (FUHeader & 0x20) != 0;

                            //if (Receiver) throw new InvalidOperationException("Receiver Bit Set");

                            //Move to data
                            ++offset;

                            //Todo Determine if need to Order by DON first.
                            //DON Present in FU - B
                            if (nalUnitType == 29) offset += 2;

                            //Determine the fragment size
                            int fragment_size = count - offset;

                            //If the size was valid
                            if (fragment_size > 0)
                            {
                                //If the start bit was set
                                if (Start)
                                {
                                    //Reconstruct the nal header
                                    //Use the first 3 bits of the first byte and last 5 bites of the FU Header
                                    byte nalHeader = (byte)((firstByte & 0xE0) | (FUHeader & Common.Binary.FiveBitMaxValue));

                                    //Could have been SPS / PPS / SEI
                                    if (nalHeader > 5)
                                    {
                                        if (nalHeader == 6)
                                        {
                                            Buffer.WriteByte(0);
                                            containsSei = true;
                                        }
                                        else if (nalHeader == 7)
                                        {
                                            Buffer.WriteByte(0);
                                            containsPps = true;
                                        }
                                        else if (nalHeader == 8)
                                        {
                                            Buffer.WriteByte(0);
                                            containsSps = true;
                                        }
                                    }

                                    if (nalHeader == 1) containsSlice = true;

                                    if (nalHeader == 5) isIdr = true;

                                    //Write the start code
                                    Buffer.Write(NalStart, 0, 3);

                                    //Write the re-construced header
                                    Buffer.WriteByte(nalHeader);
                                }

                                //Write the data of the fragment.
                                Buffer.Write(packetData, offset, fragment_size);
                            }
                        }
                        return;
                    }
                default:
                    {
                        // 6 SEI, 7 and 8 are SPS and PPS
                        if (nalUnitType > 5)
                        {
                            if (nalUnitType == 6)
                            {
                                Buffer.WriteByte(0);
                                containsSei = true;
                            }
                            else if (nalUnitType == 7)
                            {
                                Buffer.WriteByte(0);
                                containsPps = true;
                            }
                            else if (nalUnitType == 8)
                            {
                                Buffer.WriteByte(0);
                                containsSps = true;
                            }
                        }

                        if (nalUnitType == 1) containsSlice = true;

                        if (nalUnitType == 5) isIdr = true;

                        //Write the start code
                        Buffer.Write(NalStart, 0, 3);

                        //Write the nal heaer and data data
                        Buffer.Write(packetData, offset, count - offset);

                        return;
                    }
            }
        }

        internal void DisposeBuffer()
        {
            if (Buffer != null)
            {
                Buffer.Dispose();
                Buffer = null;
            }
        }

        public override void Dispose()
        {
            if (Disposed) return;
            base.Dispose();
            DisposeBuffer();
        }

        //To go to an Image...
        //Look for a SliceHeader in the Buffer
        //Decode Macroblocks in Slice
        //Convert Yuv to Rgb
    }

هناك أيضًا تطبيقات لمختلف RFC الأخرى التي تساعد في جعل الوسائط تلعب في وسائل الإعلام أو في برامج أخرى أو مجرد حفظها على القرص.

الكتابة إلى تنسيق الحاوية جارية.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top