2011-10-05 23 views
37

लांग कहानी:कैसे कच्चे यूडीपी पैकेट पर कार्रवाई करने के लिए इतना है कि वे एक डायरेक्टशो स्रोत फिल्टर में एक विकोडक फिल्टर द्वारा डीकोड किया जा सकता

  1. है एक H264/एमपीईजी -4 स्रोत
  2. मैं सक्षम कर सकते हैं आरटीएसपी प्रोटोकॉल के साथ इस स्रोत को जोड़ने के लिए।
  3. मैं आरटीपी प्रोटोकॉल के साथ कच्चे यूडीपी पैकेट प्राप्त करने में सक्षम हूं।
  4. फिर एक डिकोडर करने के लिए उन कच्चे यूडीपी पैकेट भेजने [h264/MPEG-4] [डी एस स्रोत फ़िल्टर]
  5. लेकिन उन "कच्चे" यूडीपी पैकेट डिकोडर द्वारा डीकोड नहीं किया जा सकता है [h264/MPEG-4] को फ़िल्टर

फौरन:

कैसे मैं उन कच्चे यूडीपी डेटा आदेश H264/एमपीईजी -4 विकोडक फिल्टर द्वारा decodable करने में कार्रवाई करते हैं? क्या कोई भी स्पष्ट रूप से उन चरणों की पहचान कर सकता है जिन्हें मुझे H264/एमपीईजी स्ट्रीम के साथ करना है?

अतिरिक्त जानकारी:

मैं FFmpeg के साथ इस करने में सक्षम हैं ... और मैं वास्तव में समझ नहीं कैसे FFmpeg प्रक्रिया कच्चे डेटा इतना है कि यह एक विकोडक द्वारा decodable कर सकते हैं।

उत्तर

95

केक की शांति!

1. डाटा प्राप्त करें

मैं देख सकते हैं, आप पहले से ही पता है कि कैसे करना है (RTSP सत्र, सेटअप एक RTP/AVP/UDP;unicast; परिवहन प्रारंभ करें, और उपयोगकर्ता डाटाग्राम प्राप्त) ... लेकिन आप में हैं संदेह, पूछो।

कोई फर्क नहीं पड़ता परिवहन (यूडीपी या टीसीपी) डेटा स्वरूप मुख्य रूप से एक ही है:

  • आरटीपी डेटा: [RTP Header - 12bytes][Video data]
  • यूडीपी: [RTP Data]
  • टीसीपी: [$ - 1byte][Transport Channel - 1byte][RTP data length - 2bytes][RTP data]

तो यूडीपी से डेटा प्राप्त करने के लिए, आपको केवल पहले 12 बाइट्स को बंद करना होगा जो आरटीपी हेडर का प्रतिनिधित्व करते हैं। लेकिन सावधान रहें, आपको वीडियो समय की जानकारी प्राप्त करने की आवश्यकता है, और एमपीईजी 4 के लिए पैकेटिज़ेशन जानकारी!

टीसीपी के लिए आपको बाइट $ प्राप्त करने तक पहले बाइट को पढ़ने की आवश्यकता है। फिर अगला बाइट पढ़ें, यह परिवहन चैनल होगा कि निम्न डेटा संबंधित है (जब सर्वर SETUP अनुरोध पर प्रतिक्रिया करता है तो यह कहता है: Transport: RTP/AVP/TCP;unicast;interleaved=0-1 इसका मतलब है कि वीडियो डेटा में TRANSPORT_CHANNEL = 0 होगा और वीडियो आरटीसीपी डेटा में TRANSPORT_CHANNEL = 1 होगा)। आप वीडियो डेटा प्राप्त करना चाहते हैं, इसलिए हम 0 की उम्मीद करते हैं ... फिर एक छोटा (2 बाइट) पढ़ें जो आरटीपी डेटा की लंबाई का प्रतिनिधित्व करता है, इसलिए उस बाइट को पढ़ें, और अब यूडीपी के समान ही करें।

2. Depacketize डेटा

H264 और MPEG4 डेटा आमतौर पर packetized रहे हैं (SDP में है packetization-mode पैरामीटर उनमें से प्रत्येक क्या मतलब मान 0, 1 और 2 हो सकता है, और, आप इसे depacketize करने के लिए कैसे HERE देख सकते हैं) क्योंकि एक निश्चित नेटवर्क सीमा है कि एक एंडपॉइंट टीसीपी या यूडीपी के माध्यम से भेज सकता है जिसे एमटीयू कहा जाता है। यह आमतौर पर 1500 बाइट या उससे कम होता है। तो यदि वीडियो फ्रेम उस से बड़ा है (और यह आमतौर पर है), तो इसे एमटीयू आकार के टुकड़ों में खंडित (पैकेटेटेड) होना चाहिए। यह टीसीपी और यूडीपी परिवहन पर एन्कोडर/स्ट्रीमर द्वारा किया जा सकता है, या आप दूसरी तरफ वीडियो फ्रेम को खंडित करने और फिर से इकट्ठा करने के लिए आईपी पर रिले कर सकते हैं ... यदि आप यूडीपी पर एक चिकनी त्रुटि प्रवण वीडियो चाहते हैं तो सबसे पहले बेहतर होगा और टीसीपी।

H264: जांच करने के लिए आरटीपी डेटा (जो UDP पर आ गया, या टीसीपी से अधिक interleaved) एक बड़ा H264 वीडियो फ्रेम का टुकड़ा पकड़ लेगा, तो आपको पता होना चाहिए कि टुकड़ा जब यह packetized है दिखता है:

H264 FRAGMENT

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...] 

अब, बाइट सरणी में पहला वीडियो डेटा प्राप्त 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 सेट है, यदि यह है, तो वह खंड अनुक्रम में पहला है। आप पहली पेलोड बाइट (3 NAL UNIT BITS) से पहले 3 बिट्स ले कर आईडीआर के एनएएल बाइट को पुनर्निर्माण के लिए इसका उपयोग करते हैं और उन्हें दूसरे पेलोड बाइट (5 NAL UNIT BITS) से अंतिम 5 बिट्स के साथ संयोजित करते हैं ताकि आपको इस [3 NAL UNIT BITS | 5 NAL UNIT BITS] की तरह बाइट मिले। फिर उस खंड से VIDEO FRAGMENT DATA के साथ पहले एनएएल बाइट को एक स्पष्ट बफर में लिखें।

तो start_bit और end_bit 0 तो बस बफर करने के लिए VIDEO FRAGMENT DATA (छोड़ने के पहले दो पेलोड बाइट्स कि टुकड़ा की पहचान) लिखना है।

तो start_bit है 0 और end_bit 1, इसका मतलब है कि यह पिछले टुकड़ा है, और तुम सिर्फ लिखने इसके VIDEO FRAGMENT DATA बफर करने के लिए (पहले दो बाइट्स कि टुकड़ा पहचान लंघन), और अब आप अपने वीडियो फ्रेम पुनर्निर्मित किया!

ध्यान रखें कि आरटीपी डेटा पहले 12 बाइट्स में आरटीपी हेडर रखता है, और यदि फ्रेम खंडित होता है, तो आप डीफ्रैग्मेंटेशन बफर में पहले दो बाइट्स कभी नहीं लिखते हैं, और आपको एनएएल बाइट का पुनर्निर्माण करने और इसे पहले लिखने की आवश्यकता है । यदि आप यहां कुछ गड़बड़ करते हैं, तो तस्वीर आंशिक होगी (इसमें से आधा भूरा या काला होगा या आप कलाकृतियों को देखेंगे)।

एमपीईजी 4: यह एक आसान है। आपको आरटीपी हेडर में MARKER_BIT की जांच करने की आवश्यकता है।वह बाइट सेट है (1) यदि वीडियो डेटा पूरे वीडियो फ्रेम का प्रतिनिधित्व करता है, और यह वीडियो डेटा के 0 एक वीडियो फ्रेम खंड है। तो इसे अचयनित करने के लिए, आपको यह देखने की ज़रूरत है कि MARKER_BIT क्या है। यदि यह 1 है, तो बस वीडियो डेटा बाइट पढ़ें।

पूरे फ्रेम:

[MARKER = 1] 

पैकेट फ्रेम:

[MARKER = 0], [MARKER = 0], [MARKER = 0], [MARKER = 1] 
कि, सभी दूसरों कि MARKER_BIT=1 के साथ पहली बार एक सहित का पालन किया है MARKER_BIT=0 पहले वीडियो फ्रेम टुकड़ा है

पहले पैकेट एक ही वीडियो फ्रेम के टुकड़े हैं। तो तुम क्या करने की जरूरत है: depacketization बफर में

  • MARKER_BIT=0 तक जगह वीडियो डेटा
  • प्लेस अगले वीडियो डेटा जहां MARKER_BIT=1 एक ही बफर में
  • Depacketization बफर अब एक पूरी MPEG4 फ्रेम रखती

3. विकोडक के लिए प्रक्रिया डेटा (एनएएल बाइट धारा)

जब आपके पास वीडियो फ्रेम को डिमैक्टेड किया गया है, आपको एनएएल बाइट स्ट्रीम बनाने की आवश्यकता है।

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

नियम: यह निम्न स्वरूप है

  • हर फ्रेम 0x000001 3 बाइट कोड के साथ कोई बात नहीं prepended किया जाना चाहिए कोडेक
  • प्रत्येक स्ट्रीम शुरू होनी चाहिए विन्यास जानकारी के साथ, H264 के लिए कि एसपीएस कर रहे हैं और पी पी एस (SDP में config पैरामीटर) है कि आदेश (SDP में sprop-parameter-sets) में फ्रेम, और MPEG4 के लिए VOS फ्रेम

तो तुम और H264 के लिए एक config बफर का निर्माण करने की जरूरत है MPEG4 3 बाइट्स 0x000001 के साथ प्रीपेड किया गया है, इसे पहले भेजें, और उसके बाद प्रत्येक 3 बाइट्स के साथ प्रत्येक डिमैक्टेड वीडियो फ्रेम को प्रीपेड करें और उसे डिकोडर को भेजें।

आप किसी भी बस टिप्पणी को स्पष्ट ... :)

+0

विस्तृत स्पष्टीकरण के लिए धन्यवाद ... मैं कोशिश करूंगा और देखेंगे कि क्या होगा ... – Novalis

+0

यह H264 के लिए काम करता है ... वैसे भी मुझे अन्य 28 की तुलना में अन्य खंड_टाइप की जांच करनी चाहिए ... – Novalis

+3

ठीक है अगर 28 नहीं है, यह पैकेटेटेड टुकड़ा नहीं है! फिर आप वीडियो डेटा का उपयोग करें। वोट ऊपर? : डी – Cipi

1

यूडीपी पैकेट के साथ आपको H.264 स्ट्रीम की बिट्स मिलती हैं, जिन्हें आप H.264 NAL Units में डिप्लेकेट करने की उम्मीद कर रहे हैं, जो कि बदले में, आप आमतौर पर अपने फ़िल्टर से डायरेक्टशो पाइपलाइन में धक्का दे रहे हैं।

एनएएल इकाइयों को मीडिया प्रकार (SPS/PPS एनएएल इकाइयों) के एक हिस्से के रूप में, और संभवतः डायरेक्टशो मीडिया नमूने के रूप में स्वरूपित किया जाएगा।

RFC 6184 - RTP Payload Format for H.264 Video में डेपैकेटिज़ेशन चरणों का वर्णन किया गया है। यह RFC 3550 - RTP: A Transport Protocol for Real-Time Applications द्वारा परिभाषित आरटीपी यातायात का पेलोड हिस्सा है।

साफ़, लेकिन काफी छोटा नहीं है।

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"/>'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 में या अन्य सॉफ़्टवेयर में मीडिया को चलाने में मदद करते हैं या बस इसे डिस्क पर सहेजते हैं।

एक कंटेनर प्रारूप में लिखना चल रहा है।