1. 程式人生 > >ffmpeg用來進行編碼解碼,live555用來進行傳輸rtsp、rtp

ffmpeg用來進行編碼解碼,live555用來進行傳輸rtsp、rtp

本文概要:

                本文介紹了一種常用成熟的多媒體解碼方案。使用live555作為流媒體資料來源,建立rtsp會話請求h264資料流。後端使用ffmpeg解碼h264流並儲存為yuv420格式。

                該方案比較成熟,可行性高,但網路相關資料較少,給初學者帶來了不小的入門難度。本文介紹了其中實現的幾個關鍵步驟和容易出現錯誤的地方。希望能給從事該方向開發的朋友有一些啟示。本文使用的開發環境Ubuntu12.04.。轉載請註明出處 CSDN--固本培元。

Live555介紹:

          Live555 是一個為流媒體提供解決方案的跨平臺的C++開源專案,它實現了對標準流媒體

傳輸協議如RTP/RTCP、RTSP、SIP等的支援。Live555實現了對多種音視訊編碼格式的音視訊資料的流化、接收和處理等支援,包括MPEG、H.263+、DV、JPEG視訊和多種音訊編碼。

ffmpeg介紹:

          FFmpeg是一個開源免費跨平臺的視訊和音訊流方案,屬於自由軟體,採用LGPL或GPL許可證(依據你選擇的元件)。它提供了錄製、轉換以及流化音視訊的完整解決方案。它包含了非常先進的音訊/視訊編解碼庫libavcodec,為了保證高可移植性和編解碼質量,libavcodec裡很多codec都是從頭開發的。

方案說明:

              有朋友說,ffmpeg也具有rtsp、rtp相關的支援,為何要使用live555作為rtsp會話來傳輸資料呢?原因如下。

             ffmpeg對rtsp、rtp的支援相對於live555的支援較弱。一個明顯的例子:對於使用者的驗證。live555有十分簡易的介面。而ffmpeg需要更加ffserver進行修改rtsp的請求。同時,ffmpeg程式碼風格使用c風格,單檔案長度大。更改雖然是可行的,但需要使用較長的時間。個人不建議這樣,畢竟有的時候可以走捷徑,我們就快點提高效率吧。

開始:

          廢話不多說了,我們開始。

         首先工程中src目錄結構如下:


          目錄包括,h264解碼,live555客戶端,SDL、Ui。本文介紹h264和live555client部分。

Live555客戶端

          在編譯完成live555之後會產生很多例程。其中便有客戶端的改寫例程。本文使用了testRTSPClient.cpp 例程進行改寫。

  1. RTSP client  
  2. testRTSPClient is a command-line program that shows you how to open and receive media streams that are specified by a RTSP URL - i.e., an URL that begins with rtsp://  
  3. In this demonstration application, nothing is done with the received audio/video data. You could, however, use and adapt this code in your own application to (for example) decode and play the received data.  

          該例程專用於接收rtsp會話資料流,用於修改十分方便。

          當接收流用於ffmpeg解碼h264時需要補償sps以及pps位,同時如果需要儲存h264流檔案需要新增起始碼:(live555官方常見問題記錄如下)

  1. I have successfully used the "testRTSPClient" demo application to receive a RTSP/RTP stream. Using this application code as a model, how can I decode the received video (and/or audio) data?  
  2. The "testRTSPClient" demo application receives each (video and/or audio) frame into a memory buffer, but does not do anything with the frame data. You can, however, use this code as a model for a 'media player' application that decodes and renders these frames. Note, in particular, the "DummySink" class that the "testRTSPClient" demo application uses - and the (non-static) "DummySink::afterGettingFrame()" function. When this function is called, a complete 'frame' (for H.264, this will be a "NAL unit") will have already been delivered into "fReceiveBuffer". Note that our "DummySink" implementation doesn't actually do anything with this data; that's why it's called a 'dummy' sink.  
  3. If you want to decode (or otherwise process) these frames, you would replace "DummySink" with your own "MediaSink" subclass. Its "afterGettingFrame()" function would pass the data (at "fReceiveBuffer", of length "frameSize") to a decoder. (A decoder would also use the "presentationTime" timestamp to properly time the rendering of each frame, and to synchronize audio and video.)  
  4. If you are receiving H.264 video data, there is one more thing that you have to do before you start feeding frames to your decoder. H.264 streams have out-of-band configuration information ("SPS" and "PPS" NAL units) that you may need to feed to the decoder to initialize it. To get this information, call "MediaSubsession::fmtp_spropparametersets()" (on the video 'subsession' object). This will give you a (ASCII) character string. You can then pass this to "parseSPropParameterSets()" (defined in the file "include/H264VideoRTPSource.hh"), to generate binary NAL units for your decoder.  

live555儲存h264檔案:

            live555在傳輸h264流時省略了起始碼,若需要儲存h264碼流的朋友並需要能使用vlc播放加入起始碼即可。

            起始碼:0x00 0x00 0x00 0x01

            (注意:0x01  在高地址)

ffmpeg解碼h264流:

                官方文件或者大部分網路資料中均有思路的提示,但是說明不夠詳細。

                上文live555官方文件中說到需要使用fmtp_sproparametersets()方法獲得sps以及pps在base64編碼後的內容,隨後呼叫par色SPropPa讓meterSets()方法還原為二進位制碼,具體h264流用於ffmpeg解碼流的sps以及pps位補償的獲取方式如下:

  1. unsigned int Num=0 ;  
  2. unsigned int &SPropRecords =Num;  
  3. SPropRecord *p_record=new SPropRecord();  
  4. p_record=parseSPropParameterSets(fSubsession.fmtp_spropparametersets(),SPropRecords);  

                   獲取sps位以及pps位後排列方式如下:

                   起始碼 sps 起始碼 pps 將此碼流補償給ffmpeg解碼器及可以正常解碼。

                   交流郵箱:[email protected]

參考程式碼:

ffmpeg解碼環境初始化

  1. void InitH264DecodeEnv()  
  2. {  
  3.     av_register_all();  
  4.     av_init_packet(&packet);  
  5.     /* find the video decoder */  
  6.     if (!(videoCodec=avcodec_find_decoder(CODEC_ID_H264))){  
  7.         printf("codec not found!");  
  8.         return ;  
  9.     }  
  10.     context= avcodec_alloc_context();  
  11.     picture = avcodec_alloc_frame();// Allocate video frame  
  12.     if (avcodec_open(context, videoCodec) < 0) {  
  13.         fprintf(stderr, "could not open codec\n");  
  14.         exit(1);  
  15.     }  
  16. }  

ffmpeg解碼網路h264流至YUV420(YV12)

  1. int h264_decode_from_rtsp_and_save_yuv_serial_file( char* inputbuf,int inputbuf_size,const char *outfile,  
  2.         const char *sps,const int sps_len,const char *pps,const int pps_len)  
  3. {  
  4.     char Name[100]; memset(Name,0,100);  
  5.     Set_CONTEXT_EXTRA(context,sps,sps_len,pps,pps_len);  
  6.     packet.data = inputbuf ;  
  7.     packet.size = inputbuf_size;  
  8.     while (packet.size > 0)  
  9.     {  
  10.         char outproperties[200];  
  11.         printf("##      decode cost:%d      \n",len = avcodec_decode_video2(context, picture, &got_picture, &packet));  
  12.         if (len < 0) {  
  13.             fprintf(stderr, "Error while decoding frame %d\n", frame);  
  14.             return 0;  
  15.         }  
  16.         if (got_picture) {  
  17.             printf("saving frame %3d\n", frame);  
  18. //          SaveOneYUVImage(got_picture,context,picture,outfile);  
  19.             pgm_save(picture->data[0], picture->linesize[0],       //Y  
  20.                      context->width, context->height, outfile);  
  21.             pgm_save(picture->data[1], picture->linesize[1],  
  22.                 context->width/2, context->height/2, outfile);       //U  
  23.             pgm_save(picture->data[2], picture->linesize[2],  
  24.                 context->width/2, context->height/2, outfile);       //V  
  25.             frame++;  
  26.             sprintf(outproperties,"Got width:%d height:%d frame:num:%d cost:%d pic_type:%s \n",context->width,context->height,frame,len,  
  27.                     GetFrameTypeName(Name,picture->pict_type));  
  28.             Fprint_String(outproperties,"./tmp/outproperties.txt","a+");  
  29.         }  
  30.         else{  
  31.             sprintf(outproperties,"     NoneGot width:%d height:%d frame:num:%d cost:%d pic_type:%s \n",context->width,context->height,frame,len,  
  32.                     GetFrameTypeName(Name,picture->pict_type));  
  33.             Fprint_String(outproperties,"./tmp/outproperties.txt","a+");  
  34.             printf("this frame no picture gained \n");  
  35.         }  
  36.         packet.size -= len;  
  37.         packet.data += len;  
  38.     }  
  39. //    avcodec_close(context);  
  40. //    av_free(context);  
  41. //    av_free(picture);  
  42.     return -1 ;  
  43. }  

參考文章:

live555官網介紹的live555流解碼補償pps及sps

live555中的nal units

H264標準:

pudn原始碼

csdn原始碼

資料幀參考:

LIVE555播放:

H264中SPS/PPS作用和提取

H264的NAL技術

各種SDP協議介紹:

RTP視訊流廣播:

PPS問題參考:PPS 0 referenced decode_slice_header error   0001 sps 0001 pps

http://stackoverflow.com/questions/9618369/h-264-over-rtp-identify-sps-and-pps-frames

H264的碼流結構:

NALU詳解

264圖形系列文章