主要想实现出的demo样式为,一台设备抓屏并且将抓到的屏幕传输到另一台设备上。主要实现的环境如下:
关于代码中的 const char* url = “rtsp://192.168.83.129:8554/zyx”;
#include
#include
#include
#include
#include
using namespace std;
#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib, "legacy_stdio_definitions.lib")
#define __STDC_CONSTANT_MACROS
extern "C" { FILE __iob_func[3] = { *stdin,*stdout,*stderr }; }
//引入头文件
extern "C"
{
#include "libavformat/avformat.h"
//引入时间
#include "libavutil/time.h"
#include
#include
#include
#include
}
//引入库
#pragma comment(lib,"avformat.lib")
//工具库,包括获取错误信息等
#pragma comment(lib,"avutil.lib")
//编解码的库
#pragma comment(lib,"avcodec.lib")
#pragma comment(lib,"avdevice.lib")
#pragma comment(lib,"swscale.lib")
int main(int argc, char* argv[])
{
//推流部分
AVFormatContext* pInputFormatContext = NULL;
AVCodec* pInputCodec = NULL;
AVCodecContext* pInputCodecContex = NULL;
AVFormatContext* pOutputFormatContext = NULL;
AVCodecContext* pOutCodecContext = NULL;
AVCodec* pOutCodec = NULL;
AVStream* pOutStream = NULL;
av_register_all();
avformat_network_init();
avdevice_register_all();
//const char* out_file = "rtmp://121.5.5.221:1935/rtmp_live/zyx";
const char* out_file = "rtsp://192.168.83.129:8554/zyx"; //推流地址
int ret, i;
int videoindex = -1;
//输入(Input) 抓屏部分设置
pInputFormatContext = avformat_alloc_context();
AVDictionary* options = NULL;
AVInputFormat* ifmt = av_find_input_format("gdigrab");
av_dict_set(&options, "framerate", "25", 0);
av_dict_set(&options,"video_size","1920x1080",0);
av_dict_set(&options, "start_time_realtime", 0, 0);
if (avformat_open_input(&pInputFormatContext, "desktop", ifmt, &options) != 0) { //Grab at position 10,20 真正的打开文件,这个函数读取文件的头部并且把信息保存到我们给的AVFormatContext结构体中
printf("Couldn't open input stream.\n");
getchar();
return -1;
}
if ((ret = avformat_find_stream_info(pInputFormatContext, 0)) < 0) {
printf("Failed to retrieve input stream information");
getchar();
return -1;
}
av_dump_format(pInputFormatContext, 0, "desktop", 0);
for (i = 0; pInputFormatContext->nb_streams; i++)
if (pInputFormatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
cout << "pInputFormatContext=" << pInputFormatContext->streams[i]->codec->bit_rate << endl;
videoindex = i;
break;
}
//输入流解码器初始化
pInputCodecContex = pInputFormatContext->streams[videoindex]->codec;
pInputCodecContex->flags |= 0x00080000;
pInputCodec = avcodec_find_decoder(pInputCodecContex->codec_id);
if (pInputCodec == NULL)
{
printf("Codec not found.\n");
getchar();
return -1;
}
//打开解码器
if (avcodec_open2(pInputCodecContex, pInputCodec, NULL) < 0)
{
printf("Could not open codec.\n");
getchar();
return -1;
}
//为一帧图像分配内存
AVFrame* pFrame;
AVFrame* pFrameYUV;
pFrame = av_frame_alloc();
pFrameYUV = av_frame_alloc();//为转换来申请一帧的内存(把原始帧->YUV)
pFrameYUV->format = AV_PIX_FMT_YUV420P;
pFrameYUV->width = pInputCodecContex->width;
pFrameYUV->height = pInputCodecContex->height;
unsigned char* out_buffer = (unsigned char*)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pInputCodecContex->width, pInputCodecContex->height));
//现在我们使用avpicture_fill来把帧和我们新申请的内存来结合
avpicture_fill((AVPicture*)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pInputCodecContex->width, pInputCodecContex->height);
struct SwsContext* img_convert_ctx;
img_convert_ctx = sws_getContext(pInputCodecContex->width, pInputCodecContex->height, pInputCodecContex->pix_fmt, pInputCodecContex->width, pInputCodecContex->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
//=============================================================================================
//输出流配置
avformat_alloc_output_context2(&pOutputFormatContext, NULL, "rtsp", out_file); //RTMP 定义一个输出流信息的结构体
if (!pOutputFormatContext) {
printf("Could not create output context\n");
ret = AVERROR_UNKNOWN;
getchar();
return -1;
}
av_opt_set(pOutputFormatContext->priv_data, "rtsp_transport", "tcp", 0);
//输出流编码器配置
pOutCodec = avcodec_find_encoder(AV_CODEC_ID_H264);
if (!pOutCodec) {
printf("Can not find encoder! \n");
getchar();
return -1;
}
pOutCodecContext = avcodec_alloc_context3(pOutCodec);
//pOutCodecContext->flags |= 0x00080000;
//像素格式,
pOutCodecContext->pix_fmt = AV_PIX_FMT_YUV420P;
//size
pOutCodecContext->width = pInputCodecContex->width;
pOutCodecContext->height = pInputCodecContex->height;
//目标码率
pOutCodecContext->bit_rate = 4000000;
//每250帧插入一个I帧,I帧越小视频越小
pOutCodecContext->gop_size = 10;
//Optional Param B帧
pOutCodecContext->max_b_frames = 0; //设置B帧为0,则DTS与PTS一致
pOutCodecContext->time_base.num = 1;
pOutCodecContext->time_base.den = 25;
//pOutCodecContext->lmin = 1;
//pOutCodecContext->lmax = 50;
//最大和最小量化系数
pOutCodecContext->qmin = 10;
pOutCodecContext->qmax = 51;
if (pOutputFormatContext->oformat->flags & AVFMT_GLOBALHEADER)
pOutCodecContext->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
//avcodec_parameters_from_context(pOutStream->codecpar, pInputCodecContex);
AVDictionary* param = 0;
// //H.264
//av_opt_set(pOutCodecContext->priv_data, "preset", "superfast", 0);
av_opt_set(pOutCodecContext->priv_data, "tune", "zerolatency", 0);//实时编码
if (avcodec_open2(pOutCodecContext, pOutCodec, ¶m) < 0)
{
printf("Failed to open encoder! \n");
getchar();
return -1;
}
pOutStream = avformat_new_stream(pOutputFormatContext, pOutCodec);
if (pOutStream == NULL)
{
printf("Failed create pOutStream!\n");
getchar();
return -1;
}
pOutStream->time_base.num = 1;
pOutStream->time_base.den = 25;
pOutStream->codec = pOutCodecContext;
//写文件头
int r = avformat_write_header(pOutputFormatContext, NULL);
if (r < 0)
{
printf("Failed write header!\n");
getchar();
return -1;
}
AVPacket* packet = (AVPacket*)av_malloc(sizeof(AVPacket));
int got_picture;
AVPacket pkt;
int picture_size = avpicture_get_size(pOutCodecContext->pix_fmt, pOutCodecContext->width, pOutCodecContext->height);
av_new_packet(&pkt, picture_size);
int frame_index = 0;
while ((av_read_frame(pInputFormatContext, packet)) >= 0)
{
printf("to av_read_frame! \n");
if (packet->stream_index == videoindex)
{
//真正解码,packet to pFrame
avcodec_decode_video2(pInputCodecContex, pFrame, &got_picture, packet);
//printf("真正解码,packet to pFrame! \n%d", got_picture);
if (got_picture)
{
sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pInputCodecContex->height, pFrameYUV->data, pFrameYUV->linesize);
pFrameYUV->pts = frame_index;
int picture;
//真正编码pkt
int ret = avcodec_encode_video2(pOutCodecContext, &pkt, pFrameYUV, &picture);
//pkt.pts = frame_index;
//pkt.dts = pkt.pts;
if (ret < 0) {
printf("Failed to encode! \n");
getchar();
return -1;
}
if (picture == 1)
{
pkt.stream_index = pOutStream->index;
AVRational time_base = pOutStream->time_base;//{ 1, 1000 };
AVRational r_framerate1 = { 50, 2 };//{ 50, 2 };
int64_t calc_pts = (double)frame_index * (AV_TIME_BASE) * (1 / av_q2d(r_framerate1));
pkt.pts = av_rescale_q(calc_pts, { 1, AV_TIME_BASE }, time_base); //enc_pkt.pts = (double)(framecnt*calc_duration)*(double)(av_q2d(time_base_q)) / (double)(av_q2d(time_base));
pkt.dts = pkt.pts;
frame_index++;
ret = av_interleaved_write_frame(pOutputFormatContext, &pkt);
//av_dump_format(pOutputFormatContext, 0, out_file, 1);
if (ret < 0) {
printf("Error muxing packet\n");
break;
}
av_free_packet(&pkt);
}
}
}
av_free_packet(packet);
}
//Write file trailer
av_write_trailer(pOutputFormatContext);
sws_freeContext(img_convert_ctx);
//fclose(fp_yuv);
av_free(out_buffer);
av_free(pFrameYUV);
av_free(pFrame);
avcodec_close(pInputCodecContex);
avformat_close_input(&pInputFormatContext);
avcodec_close(pOutStream->codec);
av_free(pOutCodec);
avcodec_close(pOutCodecContext);
avformat_free_context(pOutputFormatContext);
return 0;
}
由于在传输中,无法实现点对点的传输,所以是先将屏幕推流到虚拟机上的rtsp服务器上,另一台windows再从该流媒体服务器上拉流
关于rtsp服务器的搭建直接使用github上的开源项目即可,提供一下自己用的方法:
首先,下载该安装包到centos7
https://github.com/aler9/rtsp-simple-server/releases
tar zxvf rtsp-simple-server_v0.20.0_linux_amd64.tar.gz //解压
./rtsp-simple-server //运行
如果无法正常连接,要关闭防火墙
systemctl stop firewalld.service
#include
#include
#include
#include
#include
#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib, "legacy_stdio_definitions.lib")
#define __STDC_CONSTANT_MACROS
extern "C" { FILE __iob_func[3] = { *stdin,*stdout,*stderr }; }
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavdevice/avdevice.h"
#include "SDL2/SDL.h"
};
#define KEY_DOWN(VK_NONAME) ((GetAsyncKeyState(VK_NONAME) & 0x8000) ? 1:0) //必要
//Refresh Event
#define SFM_REFRESH_EVENT (SDL_USEREVENT + 1)
#define SFM_BREAK_EVENT (SDL_USEREVENT + 2)
int thread_exit=0;
int sfp_refresh_thread(void *opaque){
thread_exit=0;
while (!thread_exit) {
SDL_Event event;
event.type = SFM_REFRESH_EVENT;
SDL_PushEvent(&event);
SDL_Delay(1);
}
thread_exit=0;
//Break
SDL_Event event;
event.type = SFM_BREAK_EVENT;
SDL_PushEvent(&event);
return 0;
}
int main(int argc, char* argv[])
{
//------------SDL----------------
int screen_w,screen_h;
SDL_Window *screen;
SDL_Renderer* sdlRenderer;
SDL_Texture* sdlTexture;
SDL_Rect sdlRect;
SDL_Thread *video_tid;
SDL_Event event;
struct SwsContext *img_convert_ctx;
//const char* url = "rtmp://121.5.5.221:1935/rtmp_live/zyx";
const char* url = "rtsp://192.168.83.129:8554/zyx";
av_register_all();
avformat_network_init();
pFormatCtx = avformat_alloc_context();
avdevice_register_all();
//AVInputFormat* ifmt = av_find_input_format("gdigrab");
AVDictionary* avdic = NULL;
char option_key[] = "rtsp_transport";
char option_value[] = "tcp";
av_dict_set(&avdic, option_key, option_value, 0);
/*char option_key2[] = "max_delay";
char option_value2[] = "0";
av_dict_set(&avdic, option_key2, option_value2, 0);*/
if (avformat_open_input(&pFormatCtx, url, 0, &avdic) != 0) {
printf("Couldn't open input stream.\n");
return -1;
}
if(avformat_find_stream_info(pFormatCtx,NULL)<0){
printf("Couldn't find stream information.\n");
return -1;
}
videoindex=-1;
for(i=0; i<pFormatCtx->nb_streams; i++)
if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO){
videoindex=i;
break;
}
if(videoindex==-1){
printf("Didn't find a video stream.\n");
return -1;
}
pCodecCtx=pFormatCtx->streams[videoindex]->codec;
pCodecCtx->flags |= 0x00080000;
pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
if(pCodec==NULL){
printf("Codec not found.\n");
return -1;
}
if(avcodec_open2(pCodecCtx, pCodec,NULL)<0){
printf("Could not open codec.\n");
return -1;
}
pFrame=av_frame_alloc();
pFrameYUV=av_frame_alloc();
out_buffer=(uint8_t *)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
avpicture_fill((AVPicture *)pFrameYUV, out_buffer, PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER)) {
printf( "Could not initialize SDL - %s\n", SDL_GetError());
return -1;
}
//SDL 2.0 Support for multiple windows
screen_w = 0.8*pCodecCtx->width;
screen_h = 0.8 *pCodecCtx->height;
//screen = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
//screen_w, screen_h,SDL_WINDOW_OPENGL);
screen = SDL_CreateWindow("Simplest ffmpeg player's Window", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, screen_w , screen_h, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL);
if(!screen) {
printf("SDL: could not create window - exiting:%s\n",SDL_GetError());
return -1;
}
sdlRenderer = SDL_CreateRenderer(screen, -1, 0);
//IYUV: Y + U + V (3 planes)
//YV12: Y + V + U (3 planes)
sdlTexture = SDL_CreateTexture(sdlRenderer, SDL_PIXELFORMAT_IYUV, SDL_TEXTUREACCESS_STREAMING,pCodecCtx->width,pCodecCtx->height);
sdlRect.x=0;
sdlRect.y=0;
sdlRect.w=screen_w;
sdlRect.h=screen_h;
packet=(AVPacket *)av_malloc(sizeof(AVPacket));
video_tid = SDL_CreateThread(sfp_refresh_thread,NULL,NULL);
//------------SDL End------------
//Event Loop
for (;;) {
//Wait
SDL_WaitEvent(&event);
if(event.type==SFM_REFRESH_EVENT){
//------------------------------
if(av_read_frame(pFormatCtx, packet)>=0){
if(packet->stream_index==videoindex){
ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
if(ret < 0){
printf("Decode Error.\n");
//return -1;
}
if(got_picture){
sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);
//SDL---------------------------
SDL_UpdateTexture( sdlTexture, NULL, pFrameYUV->data[0], pFrameYUV->linesize[0] );
SDL_RenderClear( sdlRenderer );
//SDL_RenderCopy( sdlRenderer, sdlTexture, &sdlRect, &sdlRect );
SDL_RenderCopy( sdlRenderer, sdlTexture, NULL, NULL);
SDL_RenderPresent( sdlRenderer );
//SDL End-----------------------
}
}
av_free_packet(packet);
}else{
//Exit Thread
break;
thread_exit=1;
}
}else if(event.type==SDL_QUIT){
break;
thread_exit=1;
}else if(event.type==SFM_BREAK_EVENT){
break;
}
}
sws_freeContext(img_convert_ctx);
SDL_Quit();
//--------------
av_frame_free(&pFrameYUV);
av_frame_free(&pFrame);
avcodec_close(pCodecCtx);
avformat_close_input(&pFormatCtx);
return 0;
}
到此,功能已经全部实现,下面写的就当作一些扩展吧
centos7上ffmpeg环境搭建
yum install epel-release
rpm -v --import http://li.nux.ro/download/nux/RPM-GPG-KEY-nux.ro
rpm -Uvh http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-5.el7.nux.noarch.rpm
yum install ffmpeg ffmpeg-devel
ffmpeg -version//出来版本即安装成功
//利用命令行直接抓屏推流
ffmpeg -f x11grab -s 800x600 -framerate 12 -i :0.0+100,200 -preset ultrafast -s 800x600 -threads 10 -codec:v h264 -f rtsp rtsp://localhost:8554/zyx