一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——解码篇:(一)用ffmpeg解码视频
2016-02-23 11:30
706 查看
一、概述
myRTSPClient(RTSPClient)获取音视频数据之后,接下来的工作便是将音视频数据交给解码器去解码(ffmpeg),ffmpeg解码之后于是便有了呈现在终端用户(USER)面前的视频(Video)和音频(Audio),具体过程如下图所示。
关于myRTSPClient从RTSP Server那里接收多媒体数据的过程,在《收流篇》中已经做了基本介绍了。接下来,我们来讨论当RTSPClient获取到多媒体数据之后,是怎么将数据交给解码器的。首先介绍视频部分。
二、代码示例(源码见‘附录’)
整体的代码结构如下:
其中第1、2、8部分是以下要讨论的重点,其他部分均为ffmpeg的解码内容,这里有一片不错的博客,以供参考。
第一部分:'RTSP Client' send PLAY command to 'RTSP Server';
首先,我们需要向RTSP Server发送PLAY命令,让RTSP Server给RTSPClient发送多媒体数据。
该函数接受2个参数,第1个参数为myRtspClient的对象,第2个为一个RTSP URI。该函数的具体内容如下:
该函数首先给RTSP Client设置好RTSP URI,然后让Client按照RTSP的播放流程,分别给RTSP Server发送一系列命令,然后播放"video“。该函数被调用之后,RTSP Server就开始不停的向RTSP Client发送多媒体数据了。
第二部分:Register callback function for ffmpeg;
现在,客户端已经可以接收到服务端发送过来的多媒体数据了,接下来的工作就是将多媒体数据交给解码器。网上有很多关于ffmpeg的解码示例,不过都是直接读取音视频文件的。但是我们现在的音视频数据并不是什么具体的文件,而是写在内存里的。要让ffmpeg直接从内存而不是从某个文件里获取多媒体数据,我们需要对ffmpeg做一些设置。
在我们的代码示例中,用ffmpeg解码部分的代码基本是照搬ffmpeg教程中的示例,唯独以上5行代码是新添加的内容。其中的关键是pFormatCtx->pb这个数据结构。这个数据结构指定了frame的buffer,处理frame的回调函数等一系列解码细节。所以我们需要修改这个结构体让ffmpeg从RTSP Client获取多媒体数据,从而完成多媒体数据从RTSP Client交接到ffmpeg的过程。
以上代码完成了2个任务,第一个是指定解码缓存的大小(“32768”),第二个是指定了ffmpeg获取多媒体数据的回调函数(fill_iobuffer)以及该回调函数的第一个参数(&Client)。
这个回调函数由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'
向RTSP Server发送TEARDOWN命令,从而结束此次会话。
附录:
注:兼容myRtspClient-1.2.1及以上版本,且仅支持h264视频。
下载源码以及Makefile:
http://pan.baidu.com/s/1bogSD7T
下一篇:用ffmpeg解码音频
myRTSPClient(RTSPClient)获取音视频数据之后,接下来的工作便是将音视频数据交给解码器去解码(ffmpeg),ffmpeg解码之后于是便有了呈现在终端用户(USER)面前的视频(Video)和音频(Audio),具体过程如下图所示。
关于myRTSPClient从RTSP Server那里接收多媒体数据的过程,在《收流篇》中已经做了基本介绍了。接下来,我们来讨论当RTSPClient获取到多媒体数据之后,是怎么将数据交给解码器的。首先介绍视频部分。
二、代码示例(源码见‘附录’)
整体的代码结构如下:
'RTSP Client' send PLAY command to 'RTSP Server'; Register callback function for ffmpeg; Initialize ffmpeg and SDL; while(get frame) { ffmpeg loop; } free ffmpeg; '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发送多媒体数据。
rtspClientRequest(&Client, argv[1]);
该函数接受2个参数,第1个参数为myRtspClient的对象,第2个为一个RTSP URI。该函数的具体内容如下:
int rtspClientRequest(RtspClient * Client, string url) { if(!Client) return -1; // cout << "Start play " << url << endl; string RtspUri(url); // string RtspUri("rtsp://192.168.81.145/ansersion"); /* Set up rtsp server resource URI */ Client->SetURI(RtspUri); /* Send DESCRIBE command to server */ Client->DoDESCRIBE(); /* Parse SDP message after sending DESCRIBE command */ Client->ParseSDP(); /* Send SETUP command to set up all 'audio' and 'video' * sessions which SDP refers. */ Client->DoSETUP(); /* Send PLAY command to play only 'video' sessions.*/ Client->DoPLAY("video"); return 0; }
该函数首先给RTSP Client设置好RTSP URI,然后让Client按照RTSP的播放流程,分别给RTSP Server发送一系列命令,然后播放"video“。该函数被调用之后,RTSP Server就开始不停的向RTSP Client发送多媒体数据了。
第二部分:Register callback function for ffmpeg;
现在,客户端已经可以接收到服务端发送过来的多媒体数据了,接下来的工作就是将多媒体数据交给解码器。网上有很多关于ffmpeg的解码示例,不过都是直接读取音视频文件的。但是我们现在的音视频数据并不是什么具体的文件,而是写在内存里的。要让ffmpeg直接从内存而不是从某个文件里获取多媒体数据,我们需要对ffmpeg做一些设置。
pFormatCtx = NULL; pFormatCtx = avformat_alloc_context(); unsigned char * iobuffer = (unsigned char *)av_malloc(32768); AVIOContext * avio = avio_alloc_context(iobuffer, 32768, 0, &Client, fill_iobuffer, NULL, NULL); pFormatCtx->pb = avio;
在我们的代码示例中,用ffmpeg解码部分的代码基本是照搬ffmpeg教程中的示例,唯独以上5行代码是新添加的内容。其中的关键是pFormatCtx->pb这个数据结构。这个数据结构指定了frame的buffer,处理frame的回调函数等一系列解码细节。所以我们需要修改这个结构体让ffmpeg从RTSP Client获取多媒体数据,从而完成多媒体数据从RTSP Client交接到ffmpeg的过程。
以上代码完成了2个任务,第一个是指定解码缓存的大小(“32768”),第二个是指定了ffmpeg获取多媒体数据的回调函数(fill_iobuffer)以及该回调函数的第一个参数(&Client)。
int fill_iobuffer(void * opaque, uint8_t * buf, int bufsize) { size_t size = 0; if(!opaque) return -1; RtspClient * Client = (RtspClient *)opaque; // while(true) { // if(Client->GetMediaData("video", buf, &size, bufsize)) break; // } Client->GetMediaData("video", buf, &size, bufsize); printf("fill_iobuffer size: %u\n", size); return size; }
这个回调函数由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'
Client.DoTEARDOWN();
向RTSP Server发送TEARDOWN命令,从而结束此次会话。
附录:
extern "C" { #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libswscale/swscale.h> } #include <SDL.h> #include <SDL_thread.h> #ifdef __MINGW32__ #undef main /* Prevents SDL from overriding main() */ #endif #include <stdio.h> // compatibility with newer API #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1) #define av_frame_alloc avcodec_alloc_frame #define av_frame_free avcodec_free_frame #endif #include "rtspClient.h" #include <iostream> #include <string> using namespace std; // FILE * fp_open; int rtspClientRequest(RtspClient * Client, string url); int fill_iobuffer(void * opaque, uint8_t * buf, int bufsize); int fill_iobuffer(void * opaque, uint8_t * buf, int bufsize) { size_t size = 0; if(!opaque) return -1; RtspClient * Client = (RtspClient *)opaque; // while(true) { // if(Client->GetMediaData("video", buf, &size, bufsize)) break; // } Client->GetMediaData("video", buf, &size, bufsize); printf("fill_iobuffer size: %u\n", size); return size; } int main(int argc, char *argv[]) { AVFormatContext *pFormatCtx = NULL; int i, videoStream; AVCodecContext *pCodecCtxOrig = NULL; AVCodecContext *pCodecCtx = NULL; AVCodec *pCodec = NULL; AVFrame *pFrame = NULL; AVPacket packet; int frameFinished; float aspect_ratio; struct SwsContext *sws_ctx = NULL; AVInputFormat *piFmt = NULL; RtspClient Client; if(argc != 2) { cout << "Usage: " << argv[0] << " <URL>" << endl; cout << "For example: " << endl; cout << argv[0] << " rtsp://127.0.0.1/ansersion" << endl; return 1; } rtspClientRequest(&Client, argv[1]); SDL_Overlay *bmp; SDL_Surface *screen; SDL_Rect rect; SDL_Event event; // if(argc < 2) { // fprintf(stderr, "Usage: test <file>\n"); // exit(1); // } // Register all formats and codecs av_register_all(); if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) { fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError()); exit(1); } // Open video file // if(avformat_open_input(&pFormatCtx, argv[1], NULL, NULL)!=0) // return -1; // Couldn't open file // fp_open = fopen("test_packet_recv.h264", "rb+"); pFormatCtx = NULL; pFormatCtx = avformat_alloc_context(); unsigned char * iobuffer = (unsigned char *)av_malloc(32768); AVIOContext * avio = avio_alloc_context(iobuffer, 32768, 0, &Client, fill_iobuffer, NULL, NULL); pFormatCtx->pb = avio; if(!avio) { printf("avio_alloc_context error!!!\n"); return -1; } if(av_probe_input_buffer(avio, &piFmt, "", NULL, 0, 0) < 0) { printf("av_probe_input_buffer error!\n"); return -1; } else { printf("probe success\n"); printf("format: %s[%s]\n", piFmt->name, piFmt->long_name); } cout << "before avformat_open_input" << endl; int err = avformat_open_input(&pFormatCtx, "nothing", NULL, NULL); if(err) { printf("avformat_open_input error: %d\n", err); return -1; } cout << "before avformat_find_stream_info" << endl; // Retrieve stream information if(avformat_find_stream_info(pFormatCtx, NULL)<0) { printf("avformat_find_stream_info error!!!\n"); return -1; // Couldn't find stream information } // cout << "before av_dump_format" << endl; // Dump information about file onto standard error av_dump_format(pFormatCtx, 0, "", 0); // Find the first video stream videoStream=-1; // cout << "before for(i = 0; i < pFormatCtx->nb_streams; i++)" << endl; for(i=0; i<pFormatCtx->nb_streams; i++) if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) { videoStream=i; break; } if(videoStream==-1) { printf("videoStream error!!!\n"); return -1; // Didn't find a video stream } // Get a pointer to the codec context for the video stream pCodecCtxOrig=pFormatCtx->streams[videoStream]->codec; // Find the decoder for the video stream // cout << "before avcodec_find_decoder" << endl; pCodec=avcodec_find_decoder(pCodecCtxOrig->codec_id); if(pCodec==NULL) { fprintf(stderr, "Unsupported codec!\n"); return -1; // Codec not found } // Copy context pCodecCtx = avcodec_alloc_context3(pCodec); // cout << "before avcodec_copy_context" << endl; if(avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0) { fprintf(stderr, "Couldn't copy codec context"); return -1; // Error copying codec context } // Open codec cout << "before avcodec_open2" << endl; if(avcodec_open2(pCodecCtx, pCodec, NULL)<0) { printf("avcodec_open2 error!!!\n"); return -1; // Could not open codec } // Allocate video frame pFrame=av_frame_alloc(); printf("Everything OK\n"); // Make a screen to put our video #ifndef __DARWIN__ screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, 0); #else screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 24, 0); #endif if(!screen) { fprintf(stderr, "SDL: could not set video mode - exiting\n"); exit(1); } // Allocate a place to put our YUV image on that screen bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height, SDL_YV12_OVERLAY, screen); // initialize SWS context for software scaling sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P, SWS_BILINEAR, NULL, NULL, NULL ); // Read frames and save first five frames to disk i=0; while(av_read_frame(pFormatCtx, &packet)>=0) { // Is this a packet from the video stream? if(packet.stream_index==videoStream) { // Decode video frame avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet); // Did we get a video frame? if(frameFinished) { SDL_LockYUVOverlay(bmp); AVPicture pict; pict.data[0] = bmp->pixels[0]; pict.data[1] = bmp->pixels[2]; pict.data[2] = bmp->pixels[1]; pict.linesize[0] = bmp->pitches[0]; pict.linesize[1] = bmp->pitches[2]; pict.linesize[2] = bmp->pitches[1]; // Convert the image into YUV format that SDL uses sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pict.data, pict.linesize); SDL_UnlockYUVOverlay(bmp); rect.x = 0; rect.y = 0; rect.w = pCodecCtx->width; rect.h = pCodecCtx->height; SDL_DisplayYUVOverlay(bmp, &rect); } } // Free the packet that was allocated by av_read_frame av_free_packet(&packet); SDL_PollEvent(&event); switch(event.type) { case SDL_QUIT: SDL_Quit(); exit(0); break; default: break; } } // Free the YUV frame av_frame_free(&pFrame); // Close the codec avcodec_close(pCodecCtx); avcodec_close(pCodecCtxOrig); // Close the video file avformat_close_input(&pFormatCtx); Client.DoTEARDOWN(); return 0; } int rtspClientRequest(RtspClient * Client, string url) { if(!Client) return -1; // cout << "Start play " << url << endl; string RtspUri(url); // string RtspUri("rtsp://192.168.81.145/ansersion"); /* Set up rtsp server resource URI */ Client->SetURI(RtspUri); /* Send DESCRIBE command to server */ Client->DoDESCRIBE(); /* Parse SDP message after sending DESCRIBE command */ Client->ParseSDP(); /* Send SETUP command to set up all 'audio' and 'video' * sessions which SDP refers. */ Client->DoSETUP(); /* Send PLAY command to play only 'video' sessions.*/ Client->DoPLAY("video"); return 0; }
注:兼容myRtspClient-1.2.1及以上版本,且仅支持h264视频。
下载源码以及Makefile:
http://pan.baidu.com/s/1bogSD7T
下一篇:用ffmpeg解码音频
相关文章推荐
- 安装php 找不到lib.so包原因分析
- php分享二十二:php面向对象
- Jenkins进阶系列之——05FTP publisher plugin插件
- Jenkins进阶系列之——04Publish Over FTP Plugin插件
- Yii2中request的使用
- php中常用的处理字符串的函数
- eerTyraniBfohtpeDmuminiM.111
- phpstorm的第一个程序:helloworld
- PHP基于数组的分页函数(核心函数array_slice())
- 尝试phpStorm编程
- FTP
- php基于Fleaphp框架实现cvs数据导入MySQL的方法
- yii2.0-rules验证规则应用实例
- PHP的异常处理、错误的抛出及错误回调函数
- PHP编写RESTful接口
- PHP实现查询两个数组中不同元素的方法
- Yii2.0数据库查询实例(三)
- 利用php实现最简单的爬虫
- FTP的主动模式(PORT Mode)及被动模式(Passive Mode)
- PHP查询并删除数据库多列重复数据的方法(利用数组函数实现)