ffmpeg使用一:录屏保存为yuv420p

       最近要实现屏幕广播功能,本来的想法是截屏发送图片,只要1秒内能达到25张图片,就能观看到连续的广播,但由于机器可能不在一个网段内,无法用udp广播,二用tcp循环发送,会有延迟,且只要其中一个机器的网络不好,就会影响后续的发送。

        故改为搭建流媒体服务器,因流媒体服务器的技术十分成熟,只要录屏数据上传到流媒体服务器,其他机器与之连接就可以了,不用再考虑如何转发及延迟的问题,流媒体服务器已经帮我们做好了。

        思路是: 录屏+编码(压缩)+推流(至流媒体),主要技术是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={窗口名称}”:抓取屏幕中特定的一个窗口(目前中文窗口还有乱码问题)。

  1. //Use gdigrab  
  2.  AVDictionary* options = NULL;  
  3.  //Set some options  
  4.  //grabbing frame rate  
  5.  //av_dict_set(&options,"framerate","25",0);  
  6.  //The distance from the left edge of the screen or desktop  
  7.  //av_dict_set(&options,"offset_x","20",0);  
  8.  //The distance from the top edge of the screen or desktop  
  9.  //av_dict_set(&options,"offset_y","40",0);  
  10.  //Video frame size. The default is to capture the full screen  
  11.  //av_dict_set(&options,"video_size","640x480",0);  
  12.  AVInputFormat *ifmt=av_find_input_format("gdigrab");  
  13.  if(avformat_open_input(&pFormatCtx,"desktop",ifmt,&options)!=0){  
  14.   printf("Couldn't open input stream.(无法打开输入流)\n");  
  15.   return -1;  
  16.   } 
使用dshow抓屏需要安装抓屏软件:screen-capture-recorder
软件地址: http://sourceforge.net/projects/screencapturer/
下载软件安装完成后,可以指定dshow的输入设备为“screen-capture-recorder”即可。
[cpp] view plain copy
  1. AVInputFormat *ifmt=av_find_input_format("dshow");  
  2.  if(avformat_open_input(&pFormatCtx,"video=screen-capture-recorder",ifmt,NULL)!=0){  
  3.   printf("Couldn't open input stream.(无法打开输入流)\n");  
  4.   return -1;  
  5.  } 
//

扩展二、

如果把抓屏改为网络流:如

    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播放器,进行播放测试


你可能感兴趣的:(linux)