45

長い話:

  1. H264/MPEG-4 ソースあり
  2. このソースを RTSP プロトコルに接続できます。
  3. RTP プロトコルで生の UDP パケットを取得できます。
  4. 次に、これらの未加工の UDP パケットを Decoder[h264/mp​​eg-4] [DS Source Filter] に送信します。
  5. しかし、これらの「生の」UDP パケットは、Decoder[h264/mp​​eg-4] フィルターではデコードできません。

まもなく:

H264/MPEG-4 デコーダ フィルタでデコードできるようにするために、これらの未加工の UDP データを処理するにはどうすればよいですか? H264/MPEG ストリームに必要な手順を明確に特定できる人はいますか?

追加情報:

私はFFmpegでこれを行うことができます...しかし、FFmpegが生データを処理してデコーダーでデコードできるようにする方法を実際には理解できません。

4

4 に答える 4

123

ケーキの平和!

1. データを取得する

ご覧のとおり、その方法 (RTSP セッションの開始、トランスポートのセットアップ、ユーザー データグラムの取得) は既にご存じのはずですが、不明な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 からデータを取得するには、RTP ヘッダーを表す最初の 12 バイトを取り除くだけで済みます。ただし、ビデオのタイミング情報を取得するために必要であり、MPEG4 の場合はパケット化情報を取得するために必要です。

TCP の場合、 byte を取得するまで最初のバイトを読み取る必要があります$。次に、次のデータが属するトランスポート チャネルとなる次のバイトを読み取ります (サーバーが SETUP 要求に応答すると、次のように表示されます。Transport: RTP/AVP/TCP;unicast;interleaved=0-1これは、VIDEO DATA が TRANSPORT_CHANNEL=0 になり、VIDEO RTCP DATA が TRANSPORT_CHANNEL=1 になることを意味します)。VIDEO DATA を取得したいので、0 を期待します...次に、後続の RTP データの長さを表す 1 つの short (2 バイト) を読み取り、そのバイト数を読み取り、UDP の場合と同じことを行います。

2. データのデパケット化

H264 および MPEG4 データは通常、パケット化されます (SDP にはpacketization-mode、値 0、1、および 2 を持つことができるパラメータがあり、それぞれの意味と、それをデパケット化する方法については、こちらを参照してください) 1 つのエンドポイントでできる特定のネットワーク制限があるためです。 MTUと呼ばれるTCPまたはUDPを介して送信します。通常は 1500 バイト以下です。そのため、ビデオ フレームがそれよりも大きい場合 (通常は大きい)、MTU サイズのフラグメントにフラグメント化 (パケット化) する必要があります。これは、TCP および UDP トランスポートのエンコーダー/ストリーマーによって実行できます。または、IP でリレーして反対側のビデオ フレームをフラグメント化し、再構築することもできます。そしてTCP。

H264: RTP データ (UDP 経由で到着した、または TCP 経由でインターリーブされた) が 1 つの大きな 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 DATADataを取得し、次の情報を取得します。

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設定されている場合、そのフラグメントはシーケンスの最初のフラグメントです。これを使用して、最初のペイロード バイト ( ) から最初の 3 ビットを取得し、 3 NAL UNIT BITS2 番目のペイロード バイト ( ) の最後の 5 ビットと組み合わせて、 IDR の NAL バイトを再構築します。5 NAL UNIT BITSしたがって、このようなバイトが得られます[3 NAL UNIT BITS | 5 NAL UNIT BITS]。次に、最初にその NAL バイトをVIDEO FRAGMENT DATA、そのフラグメントからのクリア バッファに書き込みます。

start_bitとが 0 の場合は、 (フラグメントを識別する最初の 2 つのペイロード バイトをスキップして) をバッファend_bitに書き込むだけです。VIDEO FRAGMENT DATA

start_bitが 0 で1 の場合end_bit、それは最後のフラグメントであることを意味し、VIDEO FRAGMENT DATA(フラグメントを識別する最初の 2 バイトをスキップして) バッファに書き込むだけで、ビデオ フレームが再構築されます!

RTP データは最初の 12 バイトに RTP ヘッダーを保持すること、およびフレームがフラグメント化されている場合、最初の 2 バイトを最適化バッファーに書き込むことはなく、NAL バイトを再構築して最初に書き込む必要があることに注意してください。ここで何かを台無しにすると、画像が部分的になります (半分がグレーまたは黒になるか、アーティファクトが表示されます)。

MPEG4: これは簡単です。RTP ヘッダーの MARKER_BIT を確認する必要があります。そのバイトは1、ビデオ データがビデオ フレーム全体を表し、0ビデオ データが 1 つのビデオ フレーム フラグメントである場合に設定 ( ) されます。したがって、それをデパケット化するには、MARKER_BIT が何であるかを確認する必要があります。それだけの場合は1、ビデオ データ バイトを読み取るだけです。

フレーム全体:

   [MARKER = 1]

パケット化されたフレーム:

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

持つ最初のパケットMARKER_BIT=0は最初のビデオ フレーム フラグメントであり、最初のパケットを含む後続のすべてのパケットMARKER_BIT=1は、同じビデオ フレームのフラグメントです。だからあなたがする必要があるのは:

  • MARKER_BIT=0VIDEO DATAをデパケタイズバッファに入れるまで
  • 次の VIDEO DATAMARKER_BIT=1を同じバッファに配置する
  • Depacketization バッファが 1 つの MPEG4 フレーム全体を保持するようになりました

3. デコーダ用処理データ(NALバイトストリーム)

ビデオ フレームをデパケット化したら、NAL バイト ストリームを作成する必要があります。形式は次のとおりです。

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

ルール:

  • コーデックに関係なく、すべてのフレームの先頭に0x0000013 バイト コードを追加する必要があります。
  • sprop-parameter-setsH264 の場合は SPS フレームと PPS フレームがこの順序で (SDP で)、MPEG4 の場合は VOS フレーム ( configSDP のパラメータ) で、すべてのストリームは CONFIGURATION INFO で開始する必要があります。

そのため、H264 および MPEG4 用の構成バッファーを構築し、先頭に 3 バイト0x000001を追加して送信し、次にパケット化解除された各ビデオ フレームに同じ 3 バイトを先頭に追加して、それをデコーダーに送信する必要があります。

明確にする必要がある場合は、コメントしてください... :)

于 2011-10-05T22:37:56.797 に答える
4

私はこれを実装しています@ 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"/>'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
    }

メディアを MediaElement や他のソフトウェアで再生したり、単にディスクに保存したりするのに役立つ、他のさまざまな RFC の実装もあります。

コンテナ形式への書き込みが進行中です。

于 2014-11-14T18:57:12.127 に答える
3

UDPパケットを使用すると、H.264 NALユニットにデパケット化することが期待されるH.264ストリームのビットを受信します。これにより、通常、フィルターからDirectShowパイプラインにプッシュされます。

NALユニットは、DirectShowメディアサンプルとしてフォーマットされ、場合によってはメディアタイプの一部としてフォーマットされます(SPS / PPS NALユニット)。

デパケット化の手順は、RFC6184 -H.264ビデオのRTPペイロード形式で説明されています。これはRTPトラフィックのペイロード部分であり、RFC 3550で定義されています-RTP:リアルタイムアプリケーションのトランスポートプロトコル

明確ですが、それほど短くはありません。

于 2011-10-05T18:02:19.153 に答える