故改为搭建流媒体服务器,因流媒体服务器的技术十分成熟,只要录屏数据上传到流媒体服务器,其他机器与之连接就可以了,不用再考虑如何转发及延迟的问题,流媒体服务器已经帮我们做好了。
思路是: 录屏+编码(压缩)+推流(至流媒体),主要技术是ffmpeg库的使用。采用264压缩,264压缩时要求源数据为yuv420p格式;本篇先介绍如何录屏及转为yuv420p格式,并保存为文件,用yuv播放器播放测试。
一、github下载ffmpeg最新版本,并编译,此处不再介绍。
二、关于ffmpeg的介绍,可自行上网了解。
ffmpeg功能非常强大,也提供了丰富的命令行功能,后续专门介绍命令行。
直接上代码:
#include
using namespace std;
//Linux... 引用ffmpeg头文件
extern "C"
{
#include
#include
#include
#include
} //Output YUV420P #define OUTPUT_YUV420P 1 int main(int argc, char* argv[]) { //格式上问下结果体,,可以理解为存储数据流的文件,伴随整个生命周期 AVFormatContext *pFormatCtx; //编解码上下文结构体,编码时用于设定参数 AVCodecContext *pCodecCtx; //编解码器结构体,主要存储编解码器的一些信息 AVCodec *pCodec; int i, videoindex; //注册复用器和编解码器,所有的使用ffmpeg,首先必须调用这个函数 av_register_all(); //用于从网络接收数据,如果不是网络接收数据,可不用(如本例可不用) avformat_network_init(); //注册设备的函数,如用获取摄像头数据或音频等,需要此函数先注册 avdevice_register_all(); //AVFormatContext初始化,里面设置结构体的一些默认信息 pFormatCtx = avformat_alloc_context(); //Linux AVDictionary* options = NULL; //设置录屏参数,如录屏图像的大小、录屏帧率等 av_dict_set(&options,"video_size","640x480",0);//不设置代表全屏,但实际测试时,不设置默认大小为640x480 //av_dict_set(&options,"framerate","5",0); //av_dict_set(&options,"offset_x","20",0); //av_dict_set(&options,"offset_y","40",0); //我在linux录屏,使用x11grab,如在windows下需要gdigrab或dshow(需要安装抓屏软件:screen-capture-recorder)。mac下使用avfoundation,见后文 AVInputFormat *ifmt=av_find_input_format("x11grab"); if(avformat_open_input(&pFormatCtx,":0.0+10,20",ifmt,&options)!=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; } //pFormatCtx->nb_streams记录pFormatCtx->streams(类型为AVStream,可能是视频流、音频流或字幕) videoindex=-1; for(i=0; i
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; } //pFormatCtx->streams[videoindex]代表视频流的AVStream, pCodecCtx=pFormatCtx->streams[videoindex]->codec; 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; } //AVFrame是编码前的源数据(或解码后的数据) AVFrame *pFrame,*pFrameYUV; pFrame=av_frame_alloc(); //保存原始帧 pFrameYUV=av_frame_alloc();//转换成yuv后的帧,保留在此处 unsigned char *out_buffer=(unsigned char *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height)); //现在我们使用avpicture_fill来把帧和我们新申请的内存来结合 //函数的使用本质上是为已经分配的空间的结构体AVPicture挂上一段用于保存数据的空间,这个结构体中有一个指针数组data[4],挂在这个数组里。 avpicture_fill((AVPicture *)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);//以上就是为pFrameRGB挂上buffer。这个buffer是用于存缓冲数据的 // int ret, got_picture; //AVPacket代表编码后的一个包,即一帧编码为一个包 AVPacket *packet=(AVPacket *)av_malloc(sizeof(AVPacket)); #if OUTPUT_YUV420P FILE *fp_yuv=fopen("output.yuv","wb+"); #endif //SwsContext作为sws_scale的第一个参数,记录数据要转换的格式、大小及转换方式 struct SwsContext *img_convert_ctx; img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL); for (;;) { //读取一个包 if(av_read_frame(pFormatCtx, packet)>=0) { if(packet->stream_index==videoindex) { ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);//用packet充填pFrame,pFrame->data = packet->data? pFrame->linesize=packet->linesize if(ret < 0){ printf("Decode Error.\n"); return -1; } if(got_picture) { //转换,把源数据pFrame转换成pFrameYUV,pFrameYUV由前面设置格式为yuv420P sws_scale(img_convert_ctx, (const unsigned char* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize); #if OUTPUT_YUV420P int y_size=pCodecCtx->width*pCodecCtx->height; fwrite(pFrameYUV->data[0],1,y_size,fp_yuv); //Y fwrite(pFrameYUV->data[1],1,y_size/4,fp_yuv); //U fwrite(pFrameYUV->data[2],1,y_size/4,fp_yuv); //V #endif } } av_free_packet(packet); } } #if OUTPUT_YUV420P fclose(fp_yuv); #endif if(img_convert_ctx) sws_freeContext(img_convert_ctx); av_free(out_buffer); av_free(pFrame); av_free(pFrameYUV); avcodec_close(pCodecCtx); avformat_close_input(&pFormatCtx); return 0; }
扩展:一、
如果为windows录屏,改为
(1)“desktop”:抓取整张桌面。或者抓取桌面中的一个特定的区域。
(2)“title={窗口名称}”:抓取屏幕中特定的一个窗口(目前中文窗口还有乱码问题)。
扩展二、
如果把抓屏改为网络流:如
AVDictionary* options = NULL;
char filepath[]="rtmp://192.168.80.31:8811/myapp/test5";
//av_dict_set(&options,"video_size","640x480",0);
//AVInputFormat *ifmt=av_find_input_format("x11grab");
//if(avformat_open_input(&pFormatCtx,":0.0+10,20",ifmt,&options)!=0){
if(avformat_open_input(&pFormatCtx,filepath,NULL,NULL)!=0){
printf("Couldn't open input stream.\n");
return -1;
}
则可以把从网络接收的数据保存为yuv420p格式
保存后可下载yuv播放器,进行播放测试