您的位置:首页 > 编程语言 > PHP开发

一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——解码篇:(一)用ffmpeg解码视频

2016-02-23 11:30 706 查看
一、概述

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解码音频
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: