Проблема для декодирования видео H264 над RTP с FFMPEG (libavcodec)

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

  •  28-09-2019
  •  | 
  •  

Вопрос

Я установил Profile_IDC, Level_IDC, ExtraData et extraData_size of avcodeContext с набором параметра уровня профиля-ID ET SPROP SPROP.

Я разделяю декодирование кодированного среза, SPS, PPS и NAL_IDR_SLICE Packet:

В этом:

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 и GUTPICTURE> 0 (часто)

SPS и PPS:

идентичный код. Результат: потребляемые> 0 и GotPicture = 0

Это нормально, я думаю

Когда я нахожу новую пару SPS / PPS, я обновляю ExtraData и ExtraDa_size с полезными нагрузками этого пакета и их размера.

Nal_idr_slice:

Тип блока NAL составляет 28 => рамки IDR фрагментированы для того, чтобы я попробовал два метода для декодирования

1) Я префикс первый фрагмент (без заголовка RTP) с последовательностью 0x000001 и отправьте его на avcodec_decode_video. Затем я отправляю остальные фрагменты к этой функции.

2) I префикс первый фрагмент (без заголовка RTP) с последовательности 0x000001 и объединяю остальные фрагменты к нему. Я отправляю этот буфер в декодер.

В обоих случаях у меня нет ошибок (потребляемая> 0), но я обнаруживаю никакой рамки (GUTPICTURE = 0) ...

В чем проблема ?

Это было полезно?

Решение

В RTP все H264 I-Frames (IDR) обычно фрагментированы. Когда вы получите RTP, вы сначала должны пропустить заголовок (обычный первый 12 байтов), а затем добраться до единицы NAL (байт первой полезной нагрузки). Если NAL составляет 28 (1C), это означает, что следующая полезная нагрузка представляет один фрагмент H264 IDR (I-Frame) и что вам нужно собрать все из них для реконструкции H264 IDR (I-Frame).

Фрагментация происходит из-за ограниченного MTU и гораздо большего IDR. Один фрагмент может выглядеть так:

Фрагмент, который имеет начало bit = 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 Установите, если это так, то этот фрагмент является первым в последовательности. Вы используете его для реконструкции Nal Byte IDR, взяв первые 3 бита от первой полезной нагрузки. (3 NAL UNIT BITS) и объединить их с последними 5 битами от второй полезной нагрузки (5 NAL UNIT BITS) так что вы получите такое байт [3 NAL UNIT BITS | 5 NAL UNIT BITS]. Анкет Затем напишите это NAL Byte первым в четкий буфер со всеми другими байтами из этого фрагмента. Не забудьте пропустить первый байт в последовательности, поскольку она не является частью IDR, а идентифицирует только фрагмент.

Если start_bit и end_bit Являются 0, просто напишите полезную нагрузку (пропуская первую посуду полезной нагрузки, которая идентифицирует фрагмент) в буфер.

Если start_bit равно 0 и end_bit - это 1, это означает, что это последний фрагмент, и вы просто пишете его полезную нагрузку (пропуская первый байт, который идентифицирует фрагмент) в буфер, и теперь у вас есть ваш IDR Reconstried.

Если вам нужен код, просто спросите в комментарии, я опубликую его, но я думаю, что это довольно ясно, как сделать ... =)

Относительно декодирования

Сегодня мне пришло в голову, почему вы получаете ошибку при декодировании IDR (я предполагал, что вы реконструировали это хорошо). Как вы создаете свою запись конфигурации декодера AVC? Есть ли в этом LIB, который вы используете, это автоматизировано? Если нет, и вы не слышали об этом, продолжайте читать ...

AVCDCR указан, чтобы позволить декодерам быстро анализировать все данные, необходимые для декодирования видеопотока H264 (AVC). И данные следующие:

  • Профинансировать
  • Профиль
  • LevelIDC.
  • SPS (наборы параметров последовательности)
  • PPS (наборы параметров изображения)

Все эти данные отправляются в сеанс RTSP в SDP под полями: profile-level-id и sprop-parameter-sets.

Декодирование профиля - ID уровня

Строка идентификатора уровня Prifile разделена на 3 подстроения, каждый 2 символа длиной:

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

Каждая подстрока представляет один байт в базе16Действительно Итак, если профиль IDC - 28, это означает, что это актуально 40 в base10. Позже вы будете использовать значения Base10, чтобы построить запись конфигурации декодера AVC.

Декодирование сборов-параметра

SPROPS - это обычные 2 строки (могут быть больше), которые являются разделенными запятой, и Base64 закодированДействительно Вы можете расшифровать их обоих, но нет необходимости. Ваша работа здесь просто для того, чтобы преобразовать их из строки Base64 в массив байтов для последующего использования. Теперь у вас есть 2 байтовых массива, первый массив US SPS, второй - PPS.

Строительство AVCDCR.

Теперь у вас есть все, что вам нужно для создания AVCDCR, вы начинаете с создания нового чистого буфера, теперь пишете эти вещи в нем в порядке, объясненном здесь:

1 - байт, который имеет ценность 1 и представляет версию

2 - профиль IDC Byte

3 - Prifile IOP -байт

4 - Уровень IDC Byte

5 - Байт со значением 0xff (Google The AVC Decoder Record, чтобы увидеть, что это такое)

6 - байт со значением 0xe1

7 - Короткое количество со значением длины массива SPS

8 - байтовая массива SPS

9 - Байт с количеством массивов PPS (у вас могут быть больше из них в SPROP-параметре)

10 - Коротко с длиной следующего массива PPS

11 - массив PPS

Декодирование видеопотока

Теперь у вас есть байтовый массив, который говорит декодеру, как декодировать видеопоток H264. Я верю, что вам нужно это, если ваш lib не строит его от SDP ...

Другие советы

Я не знаю о остальной части вашей реализации, но кажется вероятным, что «фрагменты», которые вы получаете, являются NAL подразделениями. Поэтому каждый может понадобиться начальный код NALU (00 00 01 или 00 00 00 01) Прилагается при реконструкции бит -стрижки перед отправкой его в FFMPEG.

Во всяком случае, вы можете найти RFC для пакетизации H264 RTP полезно:

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
    }

Существуют также реализации для различных других предложений, которые помогают заставить медиаатрагионы в медиаэлементе или в другом программном обеспечении или просто сохранять его на диск.

Продолжается запись в формат контейнера.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top