一個基於JRTPLIB的輕量級RTSP客戶端(myRTSPClient)——解碼篇:(一)用ffmpeg解碼視頻


一、概述

myRTSPClient(RTSPClient)獲取音視頻數據之后,接下來的工作便是將音視頻數據交給解碼器去解碼(ffmpeg),ffmpeg解碼之后於是便有了呈現在終端用戶(USER)面前的視頻(Video)和音頻(Audio),具體過程如下圖所示。

關於myRTSPClient從RTSP Server那里接收多媒體數據的過程,在《收流篇》中已經做了基本介紹了。接下來,我們來討論當RTSPClient獲取到多媒體數據之后,是怎么將數據交給解碼器的。首先介紹視頻部分。

 

二、代碼示例(源碼見‘附錄’)

整體的代碼結構如下:

1 'RTSP Client' send PLAY command to 'RTSP Server';
2 Register callback function for ffmpeg;
3 Initialize ffmpeg and SDL;
4 while(get frame) {
5 ffmpeg loop;    
6 }
7 free ffmpeg;
8 'RTSP Client' send TEARDOWN command to 'RTSP Server'

其中第1、2、8部分是以下要討論的重點,其他部分均為ffmpeg的解碼內容,這里有一片不錯的博客,以供參考。

 

第一部分:'RTSP Client' send PLAY command to 'RTSP Server';

首先,我們需要向RTSP Server發送PLAY命令,讓RTSP Server給RTSPClient發送多媒體數據。

1 rtspClientRequest(&Client, argv[1]);

該函數接受2個參數,第1個參數為myRtspClient的對象,第2個為一個RTSP URI。該函數的具體內容如下:

 1 int rtspClientRequest(RtspClient * Client, string url)
 2 {
 3     if(!Client) return -1; 
 4 
 5     // cout << "Start play " << url << endl;
 6     string RtspUri(url);
 7     // string RtspUri("rtsp://192.168.81.145/ansersion");
 8 
 9     /* Set up rtsp server resource URI */
10     Client->SetURI(RtspUri);
11     
12     /* Send DESCRIBE command to server */
13     Client->DoDESCRIBE();
14 
15     /* Parse SDP message after sending DESCRIBE command */
16     Client->ParseSDP();
17 
18     /* Send SETUP command to set up all 'audio' and 'video' 
19      * sessions which SDP refers. */
20     Client->DoSETUP();
21 
22     /* Send PLAY command to play only 'video' sessions.*/
23     Client->DoPLAY("video");
24 
25     return 0;
26 }

該函數首先給RTSP Client設置好RTSP URI,然后讓Client按照RTSP的播放流程,分別給RTSP Server發送一系列命令,然后播放"video“。該函數被調用之后,RTSP Server就開始不停的向RTSP Client發送多媒體數據了。

 

第二部分:Register callback function for ffmpeg;

現在,客戶端已經可以接收到服務端發送過來的多媒體數據了,接下來的工作就是將多媒體數據交給解碼器。網上有很多關於ffmpeg的解碼示例,不過都是直接讀取音視頻文件的。但是我們現在的音視頻數據並不是什么具體的文件,而是寫在內存里的。要讓ffmpeg直接從內存而不是從某個文件里獲取多媒體數據,我們需要對ffmpeg做一些設置。

1   pFormatCtx = NULL;
2   pFormatCtx = avformat_alloc_context();
3   unsigned char * iobuffer = (unsigned char *)av_malloc(32768);
4   AVIOContext * avio = avio_alloc_context(iobuffer, 32768, 0, &Client, fill_iobuffer, NULL, NULL);
5   pFormatCtx->pb = avio;

在我們的代碼示例中,用ffmpeg解碼部分的代碼基本是照搬ffmpeg教程中的示例,唯獨以上5行代碼是新添加的內容。其中的關鍵是pFormatCtx->pb這個數據結構。這個數據結構指定了frame的buffer,處理frame的回調函數等一系列解碼細節。所以我們需要修改這個結構體讓ffmpeg從RTSP Client獲取多媒體數據,從而完成多媒體數據從RTSP Client交接到ffmpeg的過程。

以上代碼完成了2個任務,第一個是指定解碼緩存的大小(“32768”),第二個是指定了ffmpeg獲取多媒體數據的回調函數(fill_iobuffer)以及該回調函數的第一個參數(&Client)。

 1 int fill_iobuffer(void * opaque, uint8_t * buf, int bufsize) {
 2     size_t size = 0;
 3     if(!opaque) return -1;
 4     RtspClient * Client = (RtspClient *)opaque;
 5     // while(true) {
 6     //  if(Client->GetMediaData("video", buf, &size, bufsize)) break;
 7     // }
 8     Client->GetMediaData("video", buf, &size, bufsize);
 9     printf("fill_iobuffer size: %u\n", size);
10     return size;
11 }

這個回調函數由ffmpeg指定格式,作用就是將多媒體數據填充進該回調函數的第2個參數指定的緩沖區(buf),第3個參數bufsize指定了該緩沖區的大小,其值就是

AVIOContext * avio = avio_alloc_context(iobuffer, 32768, 0, &Client, fill_iobuffer, NULL, NULL);

指定的"32768",第1個參數opaque就是&Client。在ffmpeg解碼的過程中,該回調函數會一直被調用,使參數buf裝載音視頻數據用於解碼。

 

第三部分:'RTSP Client' send TEARDOWN command to 'RTSP Server'

1 Client.DoTEARDOWN();

向RTSP Server發送TEARDOWN命令,從而結束此次會話。

 

附錄一:

  1 extern "C"
  2 {
  3 #include <libavcodec/avcodec.h>
  4 #include <libavformat/avformat.h>
  5 #include <libswscale/swscale.h>
  6 }
  7 
  8 #include <SDL.h>
  9 #include <SDL_thread.h>
 10 
 11 #ifdef __MINGW32__
 12 #undef main /* Prevents SDL from overriding main() */
 13 #endif
 14 
 15 #include <stdio.h>
 16 
 17 // compatibility with newer API
 18 #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
 19 #define av_frame_alloc avcodec_alloc_frame
 20 #define av_frame_free avcodec_free_frame
 21 #endif
 22 
 23 #include "rtspClient.h"
 24 #include <iostream>
 25 #include <string>
 26 using namespace std;
 27 
 28 // FILE * fp_open;
 29 int rtspClientRequest(RtspClient * Client, string url);
 30 int fill_iobuffer(void * opaque, uint8_t * buf, int bufsize);
 31 
 32 int fill_iobuffer(void * opaque, uint8_t * buf, int bufsize) {
 33     size_t size = 0;
 34     if(!opaque) return -1;
 35     RtspClient * Client = (RtspClient *)opaque;
 36     // while(true) {
 37     //     if(Client->GetMediaData("video", buf, &size, bufsize)) break;
 38     // }
 39     Client->GetMediaData("video", buf, &size, bufsize);
 40     printf("fill_iobuffer size: %u\n", size);
 41     return size;
 42 }
 43 
 44 int main(int argc, char *argv[]) {
 45   AVFormatContext *pFormatCtx = NULL;
 46   int             i, videoStream;
 47   AVCodecContext  *pCodecCtxOrig = NULL;
 48   AVCodecContext  *pCodecCtx = NULL;
 49   AVCodec         *pCodec = NULL;
 50   AVFrame         *pFrame = NULL;
 51   AVPacket        packet;
 52   int             frameFinished;
 53   float           aspect_ratio;
 54   struct SwsContext *sws_ctx = NULL;
 55 
 56   AVInputFormat *piFmt = NULL;
 57   RtspClient Client;
 58 
 59   if(argc != 2) {
 60       cout << "Usage: " << argv[0] << " <URL>" << endl;
 61       cout << "For example: " << endl;
 62       cout << argv[0] << " rtsp://127.0.0.1/ansersion" << endl;
 63       return 1;
 64   }
 65   rtspClientRequest(&Client, argv[1]);
 66 
 67   SDL_Overlay     *bmp;
 68   SDL_Surface     *screen;
 69   SDL_Rect        rect;
 70   SDL_Event       event;
 71 
 72   // if(argc < 2) {
 73   //   fprintf(stderr, "Usage: test <file>\n");
 74   //   exit(1);
 75   // }
 76   // Register all formats and codecs
 77   av_register_all();
 78   
 79   if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
 80     fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
 81     exit(1);
 82   }
 83 
 84   // Open video file
 85   // if(avformat_open_input(&pFormatCtx, argv[1], NULL, NULL)!=0)
 86   //   return -1; // Couldn't open file
 87   
 88   // fp_open = fopen("test_packet_recv.h264", "rb+");
 89   pFormatCtx = NULL;
 90   pFormatCtx = avformat_alloc_context();
 91   unsigned char * iobuffer = (unsigned char *)av_malloc(32768);
 92   AVIOContext * avio = avio_alloc_context(iobuffer, 32768, 0, &Client, fill_iobuffer, NULL, NULL);
 93   pFormatCtx->pb = avio;
 94 
 95   if(!avio) {
 96       printf("avio_alloc_context error!!!\n");
 97       return -1;
 98   }
 99 
100   if(av_probe_input_buffer(avio, &piFmt, "", NULL, 0, 0) < 0) {
101       printf("av_probe_input_buffer error!\n");
102       return -1;
103   } else {
104       printf("probe success\n");
105       printf("format: %s[%s]\n", piFmt->name, piFmt->long_name);
106   }
107 
108   cout << "before avformat_open_input" << endl;
109   int err = avformat_open_input(&pFormatCtx, "nothing", NULL, NULL);
110   if(err) {
111       printf("avformat_open_input error: %d\n", err);
112       return -1;
113   }
114 
115   cout << "before avformat_find_stream_info" << endl;
116   // Retrieve stream information
117   if(avformat_find_stream_info(pFormatCtx, NULL)<0) {
118       printf("avformat_find_stream_info error!!!\n");
119     return -1; // Couldn't find stream information
120   }
121   
122   // cout << "before av_dump_format" << endl;
123   // Dump information about file onto standard error
124   av_dump_format(pFormatCtx, 0, "", 0);
125   
126   // Find the first video stream
127   videoStream=-1;
128   // cout << "before for(i = 0; i < pFormatCtx->nb_streams; i++)" << endl;
129   for(i=0; i<pFormatCtx->nb_streams; i++)
130     if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {
131       videoStream=i;
132       break;
133     }
134   if(videoStream==-1) {
135       printf("videoStream error!!!\n");
136     return -1; // Didn't find a video stream
137   }
138   
139   // Get a pointer to the codec context for the video stream
140   pCodecCtxOrig=pFormatCtx->streams[videoStream]->codec;
141   // Find the decoder for the video stream
142   // cout << "before avcodec_find_decoder" << endl;
143   pCodec=avcodec_find_decoder(pCodecCtxOrig->codec_id);
144   if(pCodec==NULL) {
145     fprintf(stderr, "Unsupported codec!\n");
146     return -1; // Codec not found
147   }
148 
149   // Copy context
150   pCodecCtx = avcodec_alloc_context3(pCodec);
151   // cout << "before avcodec_copy_context" << endl;
152   if(avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0) {
153     fprintf(stderr, "Couldn't copy codec context");
154     return -1; // Error copying codec context
155   }
156 
157   // Open codec
158   cout << "before avcodec_open2" << endl;
159   if(avcodec_open2(pCodecCtx, pCodec, NULL)<0) {
160       printf("avcodec_open2 error!!!\n");
161     return -1; // Could not open codec
162   }
163   
164   // Allocate video frame
165   pFrame=av_frame_alloc();
166 
167   printf("Everything OK\n");
168 
169   // Make a screen to put our video
170 #ifndef __DARWIN__
171         screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, 0);
172 #else
173         screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 24, 0);
174 #endif
175   if(!screen) {
176     fprintf(stderr, "SDL: could not set video mode - exiting\n");
177     exit(1);
178   }
179   
180   // Allocate a place to put our YUV image on that screen
181   bmp = SDL_CreateYUVOverlay(pCodecCtx->width,
182                  pCodecCtx->height,
183                  SDL_YV12_OVERLAY,
184                  screen);
185 
186   // initialize SWS context for software scaling
187   sws_ctx = sws_getContext(pCodecCtx->width,
188                pCodecCtx->height,
189                pCodecCtx->pix_fmt,
190                pCodecCtx->width,
191                pCodecCtx->height,
192                PIX_FMT_YUV420P,
193                SWS_BILINEAR,
194                NULL,
195                NULL,
196                NULL
197                );
198 
199 
200 
201   // Read frames and save first five frames to disk
202   i=0;
203   while(av_read_frame(pFormatCtx, &packet)>=0) {
204     // Is this a packet from the video stream?
205     if(packet.stream_index==videoStream) {
206       // Decode video frame
207       avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
208       
209       // Did we get a video frame?
210       if(frameFinished) {
211     SDL_LockYUVOverlay(bmp);
212 
213     AVPicture pict;
214     pict.data[0] = bmp->pixels[0];
215     pict.data[1] = bmp->pixels[2];
216     pict.data[2] = bmp->pixels[1];
217 
218     pict.linesize[0] = bmp->pitches[0];
219     pict.linesize[1] = bmp->pitches[2];
220     pict.linesize[2] = bmp->pitches[1];
221 
222     // Convert the image into YUV format that SDL uses
223     sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
224           pFrame->linesize, 0, pCodecCtx->height,
225           pict.data, pict.linesize);
226 
227     SDL_UnlockYUVOverlay(bmp);
228     
229     rect.x = 0;
230     rect.y = 0;
231     rect.w = pCodecCtx->width;
232     rect.h = pCodecCtx->height;
233     SDL_DisplayYUVOverlay(bmp, &rect);
234       
235       }
236     }
237     
238     // Free the packet that was allocated by av_read_frame
239     av_free_packet(&packet);
240     SDL_PollEvent(&event);
241     switch(event.type) {
242     case SDL_QUIT:
243       SDL_Quit();
244       exit(0);
245       break;
246     default:
247       break;
248     }
249 
250   }
251   
252   // Free the YUV frame
253   av_frame_free(&pFrame);
254   
255   // Close the codec
256   avcodec_close(pCodecCtx);
257   avcodec_close(pCodecCtxOrig);
258   
259   // Close the video file
260   avformat_close_input(&pFormatCtx);
261 
262   Client.DoTEARDOWN();
263   return 0;
264 }
265 
266 int rtspClientRequest(RtspClient * Client, string url)
267 {
268     if(!Client) return -1;
269 
270     // cout << "Start play " << url << endl;
271     string RtspUri(url);
272     // string RtspUri("rtsp://192.168.81.145/ansersion");
273 
274     /* Set up rtsp server resource URI */
275     Client->SetURI(RtspUri);
276     
277     /* Send DESCRIBE command to server */
278     Client->DoDESCRIBE();
279 
280     /* Parse SDP message after sending DESCRIBE command */
281     Client->ParseSDP();
282 
283     /* Send SETUP command to set up all 'audio' and 'video' 
284      * sessions which SDP refers. */
285     Client->DoSETUP();
286 
287     /* Send PLAY command to play only 'video' sessions.*/
288     Client->DoPLAY("video");
289 
290     return 0;
291 }

 

溫馨提示:

1, 兼容myRtspClient-1.2.1及以上版本,且僅支持h264,h265視頻;

2, 示例源碼編譯需要SDL和ffmpeg,具體可參見附錄二;

3, 博主編譯環境為 x86_64位ubuntu 16.04,以供參考。

myRtspClient-1.2.3

ffmpeg-2.8.5

SDL下載源碼以及Makefile

SDL2下載源碼以及Makefile

 

附錄二:

編譯ffmpeg:

./configure --disable-yasm
make

編譯myRtspClient:

./genMakefiles linux

make

安裝SDL:

sudo apt-get install libsdl1.2-dev

配置Makefile:

將FFMPEG_DIR和MYRTSPCLIENT_DIR配置成剛剛編譯好的兩個目錄

編譯示例代碼:

make

運行:

./tutorial rtsp://192.168.2.196:8554/ansersion

(192.168.2.196為rtsp服務器)

 

 回目錄                       下一篇


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM