一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——解码篇:(一)用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[]);
该函数接受2个参数,第1个参数为myRtspClient的对象,第2个为一个RTSP URI。该函数的具体内容如下:
int rtspClientRequest(RtspClient * Client, string url)
{
if(!Client) return -; // 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 ;
}
该函数首先给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();
AVIOContext * avio = avio_alloc_context(iobuffer, , , &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 = ;
if(!opaque) return -;
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 = ;
if(!opaque) return -;
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 != ) {
cout << "Usage: " << argv[] << " <URL>" << endl;
cout << "For example: " << endl;
cout << argv[] << " rtsp://127.0.0.1/ansersion" << endl;
return ;
}
rtspClientRequest(&Client, argv[]); 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();
} // 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();
AVIOContext * avio = avio_alloc_context(iobuffer, , , &Client, fill_iobuffer, NULL, NULL);
pFormatCtx->pb = avio; if(!avio) {
printf("avio_alloc_context error!!!\n");
return -;
} if(av_probe_input_buffer(avio, &piFmt, "", NULL, , ) < ) {
printf("av_probe_input_buffer error!\n");
return -;
} 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 -;
} cout << "before avformat_find_stream_info" << endl;
// Retrieve stream information
if(avformat_find_stream_info(pFormatCtx, NULL)<) {
printf("avformat_find_stream_info error!!!\n");
return -; // Couldn't find stream information
} // cout << "before av_dump_format" << endl;
// Dump information about file onto standard error
av_dump_format(pFormatCtx, , "", ); // Find the first video stream
videoStream=-;
// cout << "before for(i = 0; i < pFormatCtx->nb_streams; i++)" << endl;
for(i=; i<pFormatCtx->nb_streams; i++)
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {
videoStream=i;
break;
}
if(videoStream==-) {
printf("videoStream error!!!\n");
return -; // 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 -; // Codec not found
} // Copy context
pCodecCtx = avcodec_alloc_context3(pCodec);
// cout << "before avcodec_copy_context" << endl;
if(avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != ) {
fprintf(stderr, "Couldn't copy codec context");
return -; // Error copying codec context
} // Open codec
cout << "before avcodec_open2" << endl;
if(avcodec_open2(pCodecCtx, pCodec, NULL)<) {
printf("avcodec_open2 error!!!\n");
return -; // 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, , );
#else
screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, , );
#endif
if(!screen) {
fprintf(stderr, "SDL: could not set video mode - exiting\n");
exit();
} // 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=;
while(av_read_frame(pFormatCtx, &packet)>=) {
// 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[] = bmp->pixels[];
pict.data[] = bmp->pixels[];
pict.data[] = bmp->pixels[]; pict.linesize[] = bmp->pitches[];
pict.linesize[] = bmp->pitches[];
pict.linesize[] = bmp->pitches[]; // Convert the image into YUV format that SDL uses
sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
pFrame->linesize, , pCodecCtx->height,
pict.data, pict.linesize); SDL_UnlockYUVOverlay(bmp); rect.x = ;
rect.y = ;
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();
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 ;
} int rtspClientRequest(RtspClient * Client, string url)
{
if(!Client) return -; // 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 ;
}
温馨提示:
1, 兼容myRtspClient-1.2.1及以上版本,且仅支持h264,h265视频;
2, 示例源码编译需要SDL和ffmpeg,具体可参见附录二;
3, 博主编译环境为 x86_64位ubuntu 16.04,以供参考。
附录二:
编译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服务器)
一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——解码篇:(一)用ffmpeg解码视频的更多相关文章
- 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(四)用户接口层之处理SDP报文
当RTSP客户端向RTSP服务端发送DESCRIBE命令时,服务端理应当回复一条SDP报文. 该SDP报文中包含RTSP服务端的基本信息.所能提供的音视频媒体类型以及相应的负载能力,以下是一段SDP示 ...
- 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(五)用户接口层之提取媒体流数据
当RTSP客户端向RTSP服务端发送完PLAY命令后,RTSP服务端就会另外开启UDP端口(SDP协商定义的端口)发送RTP媒体流数据包.这些数据包之间会间隔一段时间(毫秒级)陆续被发送到RTSP客户 ...
- 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(十)使用JRTPLIB传输RTP数据
myRtspClient通过简单修改JRTPLIB的官方例程作为其RTP传输层实现.因为JRTPLIB使用的是CMAKE编译工具,这就是为什么编译myRtspClient时需要预装CMAKE. 该部分 ...
- 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(二)用户接口层之RtspClient类及其构造函数
RtspClient类是myRTSPClient函数库所有特性集中实现的地方. 主要为用户提供: 1. RTSP协议通信接口函数,如DoOPTIONS(): 2. RTSP账号.密码设置函数,如Set ...
- 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(一)概览
myRTSPClient主要可以分成3个部分: 1. RTSPClient用户接口层: 2. RTP 音视频传输解析层: 3. RTP传输层. "RTSPClient用户接口层": ...
- 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(三)用户接口层之RTSP命令
截至版本1.2.3,myRtspClient函数库共支持以下6个RTSP命令: (1)OPTIONS (2)DESCRIBE (3)SETUP (4)PLAY (5)PAUSE (6)TEARDOWN ...
- 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(八)RTP音视频传输解析层之MPA传输格式
一.MPEG RTP音频传输 相较H264的RTP传输格式,MPEGE音频传输格式则简单许多. 每一包MPEG音频RTP包都前缀一个4字节的Header,如下图(RFC2550) “MBZ”必须为0( ...
- 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(七)RTP音视频传输解析层之H264传输格式
一.H264传输封包格式的2个概念 (1)组包模式(Packetization Modes) RFC3984中定义了3种组包模式:单NALU模式(Single Nal Unit Mode).非交错模式 ...
- 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(六)RTP音视频传输解析层之音视频数据传输格式
一.差异 本地音视频数据格式和用来传输的音视频数据格式存在些许差异,由于音视频数据流到达客户端时,需要考虑数据流的数据边界.分包.组包顺序等问题,所以传输中的音视频数据往往会多一些字节. 举个例子,有 ...
- 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——实现篇:(九)以g711-mulaw为例添加新的编码格式解析支持
一.myRtspClient音频解析架构 AudioTypeBase是处理解析各种编码的音频数据的接口类.处理MPA数据的MPEG_Audio类和处理g711-mulaw的PCMU_Audio类均从A ...
随机推荐
- FineReport中如何对cpt模板加密
1. 描述 FR客户使用FineReport报表并将其集成到自己的产品中,然后提供给最终用户使用,最终用户可以预览FR模板,但是不能打开模板进行设计修改. FineReport提供了cpt模板Des加 ...
- springmvc(一) springmvc框架原理分析和简单入门程序
springmvc这个框架真的非常简单,感觉比struts2还更简单,好好沉淀下来学习~ --WH 一.什么是springmvc? 我们知道三层架构的思想,并且如果你知道ssh的话,就会更加透彻的理解 ...
- zabbix的Java API(一)
上文说了,我是对zabbix做第二次开发的小白,既然要对zabbix做第二次开发又是小白,那么就得来研究zabbix提供的相关API了. 于是我在zabbix网站各种找,终于在下面网址找到了: htt ...
- 跨域访问http接口的使用
最近在弄一个sip网页集成版软电话,为了功能的完善,呼叫中心的工作人员为我们提供了一个http接口,我先在网页中直接打开分析了一下他的返回值,然后又放到js中去访问,结果一放到js中一访问就发现浏览器 ...
- 小哈学Python第二课:Hello Word
Python入门 1.Hello World 2.Hello World
- ES6速记手册
1.三元操作符 这是一个很好的节省代码当你想要编写一个如果. . else语句在一线. 普通写法: const x = 20;let big;if (x > 10) { 速记: const bi ...
- 玩玩微信公众号Java版之五:获取关注用户信息
在关注者与公众号产生消息交互后,公众号可获得关注者的OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的.对于不同公众号,同一用户的openid不同).公众号可通过本接口来根据Op ...
- MSDTC启用——分布式事务
一.前言 最近在做一个项目的时候使用了.NET中的System.Transactions(分布式事务),当项目开发完成以后,调用的时候遇到了MSDTC的问题,在查阅了相关资料后将这个问题解决了,大致的 ...
- python2.7 + selenium3.4.3浏览器的选择
大家都知道,selenium2对火狐浏览器兼容性比较好,和谷歌和IE相比,好处是无需安装相应的driver.exe来支持启动浏览器,但是缺点是最高支持火狐47版本. 现在selenium3出来了,是不 ...
- 内核对象kobject和sysfs(4)——kset分析
内核对象kobject和sysfs(4)--kset分析 从狭义上来说,kset就是kobj的一个再封装而已.在封装之后,提供了针对kset之下所有kobj统一管理的一些方法. 我们还是从结构说起: ...