在RTSP協議的交互過程中,第二步客戶端發送DESCRIBE請求之后,服務端會返回SDP內容,該SDP內容中有關於媒體和會話的描述,本篇文章主要給出如何從SDP字符串中得到H264視頻信息中的sps、pps的二進制數據。
我們知道,在RTSP協議中DESCRIBE請求回復內容的SDP部分中,如果服務端的直播流的視頻是H264的編碼格式的話,那么在SDP中會將H264的sps、pps信息通過Base64編碼成字符串發送給客戶端(也就是解碼器端),sps稱為序列參數集,pps稱為圖形參數集。這兩個參數中包含了初始化H.264解碼器所需要的信息參數,包括編碼所用的profile,level,圖像的寬和高,deblock濾波器等。這樣解碼器就可以在DESCRIBE階段,利用這些參數初始化解碼器的設置了。那么如何將SDP中的字符串還原成sps、pps的二進制數據呢。下面的部分代碼是從live555項目中取出來的,可以作為小功能獨立使用,如果大家有用的着,可以直接拿去使用在項目中:
//main.cpp的內容 #include <stdio.h> #include "Base64.h" int main() { /* RTSP 響應的SDP的內容中sprop-parameter-sets鍵值: sprop-parameter-sets=Z2QAKq2wpDBSAgFxQWKQPQRWFIYKQEAuKCxSB6CKwpDBSAgFxQWKQPQRTDoUKQNC4oJHMGIemHQoUgaFxQSOYMQ9MOhQpA0LigkcwYh6xEQmIVilsQRWUURJsogxOU4QITKUIEVlCCTYQVhBMJQhMIjGggWQJFaIGBJZBAaEnaMIDwsSWQQKCwsrRBQYOWQweO0YEBZASNAogszlAUAW7/wcFBwMQAABdwAAr8g4AAADAL68IAAAdzWU//+MAAADAF9eEAAAO5rKf//CgA==,aP48sA==; 其中逗號前面的內容是sps的二進制數據被base64之后的結果 而逗號后面的內容(不要分號,分號是sdp中鍵值對的分隔符),是pps的內容 使用live555中的base64Decode函數分別對這兩部分進行反base64解碼得到的二進制數據就是h264中的sps pps 的二進制內容 分別是以67 和 68 開頭 */ char * sps_sdp = "Z2QAKq2wpDBSAgFxQWKQPQRWFIYKQEAuKCxSB6CKwpDBSAgFxQWKQPQRTDoUKQNC4oJHMGIemHQoUgaFxQSOYMQ9MOhQpA0LigkcwYh6xEQmIVilsQRWUURJsogxOU4QITKUIEVlCCTYQVhBMJQhMIjGggWQJFaIGBJZBAaEnaMIDwsSWQQKCwsrRBQYOWQweO0YEBZASNAogszlAUAW7/wcFBwMQAABdwAAr8g4AAADAL68IAAAdzWU//+MAAADAF9eEAAAO5rKf//CgA=="; char * pps_sdp = "aP48sA=="; unsigned int result_size=0; unsigned char * p = base64Decode(sps_sdp,result_size);//解碼sps字符串 for(int i =0;i<result_size;i++) { printf("%02X ",p[i]); if((i+1)%16==0) { printf("\n"); } } printf("\n\n\n"); p = base64Decode(pps_sdp,result_size);//解碼pps字符串 for(int i =0;i<result_size;i++) { printf("%02X ",p[i]); if((i+1)%16==0) { printf("\n"); } } printf("\n"); return 0 ; }
/* 程序輸出如下: //這里是sps的二進制數據 67 64 00 2A AD B0 A4 30 52 02 01 71 41 62 90 3D 04 56 14 86 0A 40 40 2E 28 2C 52 07 A0 8A C2 90 C1 48 08 05 C5 05 8A 40 F4 11 4C 3A 14 29 03 42 E2 82 47 30 62 1E 98 74 28 52 06 85 C5 04 8E 60 C4 3D 30 E8 50 A4 0D 0B 8A 09 1C C1 88 7A C4 44 26 21 58 A5 B1 04 56 51 44 49 B2 88 31 39 4E 10 21 32 94 20 45 65 08 24 D8 41 58 41 30 94 21 30 88 C6 82 05 90 24 56 88 18 12 59 04 06 84 9D A3 08 0F 0B 12 59 04 0A 0B 0B 2B 44 14 18 39 64 30 78 ED 18 10 16 40 48 D0 28 82 CC E5 01 40 16 EF FC 1C 14 1C 0C 40 00 01 77 00 00 AF C8 38 00 00 03 00 BE BC 20 00 00 77 35 94 FF FF 8C 00 00 03 00 5F 5E 10 00 00 3B 9A CA 7F FF C2 80 //這里是pps的二進制數據 68 FE 3C B0 */
其中用到的一個主要函數 base64Decode 的實現如下:
#include "Base64.h" #include "strDup.h" #include <string.h> static char base64DecodeTable[256]; static void initBase64DecodeTable() { int i; for (i = 0; i < 256; ++i) base64DecodeTable[i] = (char)0x80; // default value: invalid for (i = 'A'; i <= 'Z'; ++i) base64DecodeTable[i] = 0 + (i - 'A'); for (i = 'a'; i <= 'z'; ++i) base64DecodeTable[i] = 26 + (i - 'a'); for (i = '0'; i <= '9'; ++i) base64DecodeTable[i] = 52 + (i - '0'); base64DecodeTable[(unsigned char)'+'] = 62; base64DecodeTable[(unsigned char)'/'] = 63; base64DecodeTable[(unsigned char)'='] = 0; } unsigned char* base64Decode(char const* in, unsigned& resultSize, Boolean trimTrailingZeros) { static Boolean haveInitedBase64DecodeTable = False; if (!haveInitedBase64DecodeTable) { initBase64DecodeTable(); haveInitedBase64DecodeTable = True; } unsigned char* out = (unsigned char*)strDupSize(in); // ensures we have enough space int k = 0; int const jMax = strlen(in) - 3; // in case "in" is not a multiple of 4 bytes (although it should be) for (int j = 0; j < jMax; j += 4) { char inTmp[4], outTmp[4]; for (int i = 0; i < 4; ++i) { inTmp[i] = in[i+j]; outTmp[i] = base64DecodeTable[(unsigned char)inTmp[i]]; if ((outTmp[i]&0x80) != 0) outTmp[i] = 0; // pretend the input was 'A' } out[k++] = (outTmp[0]<<2) | (outTmp[1]>>4); out[k++] = (outTmp[1]<<4) | (outTmp[2]>>2); out[k++] = (outTmp[2]<<6) | outTmp[3]; } if (trimTrailingZeros) { while (k > 0 && out[k-1] == '\0') --k; } resultSize = k; unsigned char* result = new unsigned char[resultSize]; memmove(result, out, resultSize); delete[] out; return result; } static const char base64Char[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; char* base64Encode(char const* origSigned, unsigned origLength) { unsigned char const* orig = (unsigned char const*)origSigned; // in case any input bytes have the MSB set if (orig == NULL) return NULL; unsigned const numOrig24BitValues = origLength/3; Boolean havePadding = origLength > numOrig24BitValues*3; Boolean havePadding2 = origLength == numOrig24BitValues*3 + 2; unsigned const numResultBytes = 4*(numOrig24BitValues + havePadding); char* result = new char[numResultBytes+1]; // allow for trailing '\0' // Map each full group of 3 input bytes into 4 output base-64 characters: unsigned i; for (i = 0; i < numOrig24BitValues; ++i) { result[4*i+0] = base64Char[(orig[3*i]>>2)&0x3F]; result[4*i+1] = base64Char[(((orig[3*i]&0x3)<<4) | (orig[3*i+1]>>4))&0x3F]; result[4*i+2] = base64Char[((orig[3*i+1]<<2) | (orig[3*i+2]>>6))&0x3F]; result[4*i+3] = base64Char[orig[3*i+2]&0x3F]; } // Now, take padding into account. (Note: i == numOrig24BitValues) if (havePadding) { result[4*i+0] = base64Char[(orig[3*i]>>2)&0x3F]; if (havePadding2) { result[4*i+1] = base64Char[(((orig[3*i]&0x3)<<4) | (orig[3*i+1]>>4))&0x3F]; result[4*i+2] = base64Char[(orig[3*i+1]<<2)&0x3F]; } else { result[4*i+1] = base64Char[((orig[3*i]&0x3)<<4)&0x3F]; result[4*i+2] = '='; } result[4*i+3] = '='; } result[numResultBytes] = '\0'; return result; }
完整demo下載(Ubuntu Linux下運行沒問題)