Как обрабатывать необработанные UDP-пакеты, чтобы их можно было декодировать с помощью фильтра декодера в прямом источнике фильтра источника

Длинная история:

  • Источник H264/MPEG-4
  • Я могу подключить этот источник с протоколом RTSP.
  • Я могу получить необработанные UDP-пакеты с протоколом RTP.
  • Затем отправьте эти необработанные UDP-пакеты в декодер [h264/mpeg-4] [Фильтр источника DS]
  • Но эти "необработанные" UDP-пакеты не могут быть декодированы фильтром Decoder [h264/mpeg-4].

Коротко:

Как обрабатывать эти необработанные данные UDP для декодирования с помощью фильтра декодера H264/MPEG-4? Может ли кто-нибудь четко определить шаги, которые я должен выполнить с потоком H264/MPEG?

Дополнительная информация:

Я могу с этим справиться с FFmpeg... И я не могу понять, как FFmpeg обрабатывает необработанные данные, чтобы декодировать его декодером.

Ответ 1

Мир пирога!

1. Получить данные

Как я вижу, вы уже знаете, как это сделать (запустите сеанс RTSP, SETUP a RTP/AVP/UDP;unicast; и получите пользовательские датаграммы)... но если у вас есть сомнения, спросите.

Независимо от транспорта (UDP или TCP) формат данных в основном одинаков:

  • Данные RTP: [RTP Header - 12bytes][Video data]
  • UDP: [RTP Data]
  • TCP: [$ - 1byte][Transport Channel - 1byte][RTP data length - 2bytes][RTP data]

Итак, чтобы получить данные из UDP, вам нужно снять только первые 12 байтов, которые представляют собой заголовок RTP. Но будьте осторожны, вам нужно, чтобы получить информацию о времени видео, а для MPEG4 - информацию о пакетировании!

Для TCP вам нужно прочитать первый байт, пока не получите байт $. Затем прочитайте следующий байт, который будет транспортным каналом, которому принадлежат следующие данные (когда сервер отвечает на запрос SETUP, он говорит: Transport: RTP/AVP/TCP;unicast;interleaved=0-1 это означает, что VIDEO DATA будет иметь TRANSPORT_CHANNEL = 0, а VIDEO RTCP DATA будет иметь TRANSPORT_CHANNEL = 1). Вы хотите получить VIDEO DATA, поэтому мы ожидаем 0... затем читаем один короткий (2 байт), который представляет длину данных RTP, которые следует, поэтому читайте много байтов и теперь делайте то же самое, что и для UDP.

2. Декомпозиция данных

Данные H264 и MPEG4 обычно пакетируются (в SDP есть параметр packetization-mode, который может иметь значения 0, 1 и 2, что каждый из них означает, и как депакетировать его, вы можете видеть ЗДЕСЬ), потому что существует определенный сетевой предел, который одна конечная точка может отправлять через TCP или UDP, который называется MTU. Обычно это 1500 байт или меньше. Поэтому, если видеокадр больше, чем это (и обычно это так), его необходимо фрагментировать (пакетировать) в фрагменты размера MTU. Это можно сделать с помощью кодировщика/стримера на транспорте TCP и UDP, или вы можете ретранслировать по IP для фрагментации и повторной сборки видеофрагмента с другой стороны... первый намного лучше, если вы хотите иметь плавное подверженное ошибкам видео по UDP и TCP.

H264:. Чтобы проверить, что RTP-данные (которые поступают через UDP или чередуются через TCP), содержат фрагмент одного большего кадра H264, вы должны знать, как выглядит фрагмент, когда он пакетируется:

ФРАГМЕНТ H264

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

Теперь получите первые данные VIDEO DATA в массиве байтов под названием Data и получите следующую информацию:

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, то последующие видеоданные представляют фрагмент видеофрагмента. Следующая проверка - это start_bit set, если это так, то этот фрагмент является первым в последовательности. Вы используете его для восстановления байта NAL IDR, взяв первые 3 бита из первого байта полезной нагрузки (3 NAL UNIT BITS) и объединив их с последними 5 битами из второго байта полезной нагрузки (5 NAL UNIT BITS), чтобы вы получили байт, подобный этому [3 NAL UNIT BITS | 5 NAL UNIT BITS], Затем напишите, что NAL байт сначала в ясный буфер с VIDEO FRAGMENT DATA из этого фрагмента.

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

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

Не забывайте, что данные RTP содержат заголовок RTP в первых 12 байтах и ​​что если фрагмент фрагментирован, вы никогда не записываете первые два байта в буфер дефрагментации и что вам нужно восстановить байт NAL и сначала записать его. Если вы что-то испортили, изображение будет частичным (половина его будет серым или черным или вы увидите артефакты).

MPEG4: Это легко. Вам нужно проверить MARKER_BIT в заголовке RTP. Этот байт устанавливается (1), если видеоданные представляют весь видеокадр, а 0 видеоданных - один фрагмент видеофрагмента. Чтобы депакетировать это, вам нужно посмотреть, что такое MARKER_BIT. Если это 1, то просто прочитайте байты данных видео.

ВЕСЬ РАМКА:

   [MARKER = 1]

ПАКЕТОВАЯ РАМКА:

   [MARKER = 0], [MARKER = 0], [MARKER = 0], [MARKER = 1]

Первый пакет, который имеет MARKER_BIT=0, является первым фрагментом видеофрагмента, все остальные, которые следуют, включая первый с MARKER_BIT=1, являются фрагментами одного и того же видеокадра. Итак, что вам нужно сделать:

  • Пока MARKER_BIT=0 поместите VIDEO DATA в буфер депакетизации.
  • Поместите следующие VIDEO DATA, где MARKER_BIT=1 в тот же буфер
  • В буфере декомпозиции теперь имеется один полный кадр MPEG4

3. Данные процесса для декодера (поток байтов NAL)

Когда у вас есть депакетированные видеоролики, вам нужно сделать поток байтов NAL. Он имеет следующий формат:

  • H264: 0x000001[SPS], 0x000001[PPS], 0x000001[VIDEO FRAME], 0x000001...
  • MPEG4: 0x000001[Visual Object Sequence Start], 0x000001[VIDEO FRAME]

ПРАВИЛА:

  • Каждый кадр ДОЛЖЕН быть добавлен с 0x000001 3 байтовым кодом независимо от кодека
  • Каждый поток ДОЛЖЕН начинаться с CONFIGURATION INFO, для H264, которые являются кадрами SPS и PPS в этом порядке (sprop-parameter-sets в SDP), а для MPEG4 - кадр VOS (config в SDP)

Итак, вам нужно создать буфер конфигурации для H264 и MPEG4, добавленный с 3 байтами 0x000001, сначала отправить его, а затем добавить каждый депакетированный видеокадр с теми же 3 байтами и отправить его в декодер.

Если вам нужно прояснить только комментарий...:)

Ответ 2

У меня есть реализация этого @https://net7mma.codeplex.com/

Вот соответствующий код

/// <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"/> 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, которые помогают получать медиафайлы в MediaElement или в другом программном обеспечении или просто сохранять его на диск.

Выполняется запись в формат контейнера.

Ответ 3

С UDP-пакетами вы получаете бит потока H.264, который вы, как ожидается, депакетируете в H.264 NAL Units, которые в своих вы обычно нажимаете на конвейер DirectShow из вашего фильтра.

Единицы NAL будут отформатированы как образцы мультимедиа DirectShow и, возможно, также как часть типа медиафайла (SPS/PPS NAL Units).

Шаги декомпозиции описаны в RFC 6184 - Формат полезной нагрузки RTP для видео H.264. Это часть полезной нагрузки трафика RTP, определяемая RFC 3550 - RTP: Транспортный протокол для приложений реального времени.

Ясный, но не совсем короткий.