ffmpeg超详细综合教程——摄像头直播

本文的示例将实现:读取PC摄像头视频数据并以RTMP协议发送为直播流。示例包含了
一、ffmpeg的libavdevice的使用
二、视频解码、编码、推流的基本流程
具备较强的综合性。
要使用libavdevice的相关函数,首先须要注册相关组件网络

[cpp]  view plain  copy
 
  1. avdevice_register_all();  


接下来咱们要列出电脑中可用的dshow设备ide

[cpp]  view plain  copy
 
  1. AVFormatContext *pFmtCtx = avformat_alloc_context();  
  2.     AVDeviceInfoList *device_info = NULL;  
  3.     AVDictionary* options = NULL;  
  4.     av_dict_set(&options, "list_devices", "true", 0);  
  5.     AVInputFormat *iformat = av_find_input_format("dshow");  
  6.     printf("Device Info=============\n");  
  7.     avformat_open_input(&pFmtCtx, "video=dummy", iformat, &options);  
  8.     printf("========================\n");  


能够看到这里打开设备的步骤基本与打开文件的步骤相同,上面的代码中设置了AVDictionary,这样与在命令行中输入下列命令有相同的效果函数

[cpp]  view plain  copy
 
  1. ffmpeg -list_devices true -f dshow -i dummy  


以上语句获得的结果以下

这里个人电脑上只有一个虚拟摄像头软件虚拟出来的几个dshow设备,没有音频设备,因此有如上的结果。
须要说明的是,avdevice有一个avdevice_list_devices函数能够枚举系统的采集设备,包括设备名和设备描述,很是适合用于让用户选择要使用的设备,可是不支持dshow设备,因此这里没有使用它。
下一步就能够像打开普通文件同样将上面的具体设备名做为输入打开,并进行相应的初始化设置,以下ui

[cpp]  view plain  copy
 
  1. av_register_all();  
  2.     //Register Device  
  3.     avdevice_register_all();  
  4.     avformat_network_init();  
  5.       
  6.     //Show Dshow Device    
  7.     show_dshow_device();  
  8.       
  9.     printf("\nChoose capture device: ");  
  10.     if (gets(capture_name) == 0)  
  11.     {  
  12.         printf("Error in gets()\n");  
  13.         return -1;  
  14.     }  
  15.     sprintf(device_name, "video=%s", capture_name);  
  16.   
  17.     ifmt=av_find_input_format("dshow");  
  18.       
  19.     //Set own video device's name  
  20.     if (avformat_open_input(&ifmt_ctx, device_name, ifmt, NULL) != 0){  
  21.         printf("Couldn't open input stream.(没法打开输入流)\n");  
  22.         return -1;  
  23.     }  
  24.     //input initialize  
  25.     if (avformat_find_stream_info(ifmt_ctx, NULL)<0)  
  26.     {  
  27.         printf("Couldn't find stream information.(没法获取流信息)\n");  
  28.         return -1;  
  29.     }  
  30.     videoindex = -1;  
  31.     for (i = 0; i<ifmt_ctx->nb_streams; i++)  
  32.         if (ifmt_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)  
  33.         {  
  34.             videoindex = i;  
  35.             break;  
  36.         }  
  37.     if (videoindex == -1)  
  38.     {  
  39.         printf("Couldn't find a video stream.(没有找到视频流)\n");  
  40.         return -1;  
  41.     }  
  42.     if (avcodec_open2(ifmt_ctx->streams[videoindex]->codec, avcodec_find_decoder(ifmt_ctx->streams[videoindex]->codec->codec_id), NULL)<0)  
  43.     {  
  44.         printf("Could not open codec.(没法打开解码器)\n");  
  45.         return -1;  
  46.     }  

在选择了输入设备并进行相关初始化以后,须要对输出作相应的初始化。ffmpeg将网络协议和文件同等看待,同时由于使用RTMP协议进行传输,这里咱们指定输出为flv格式,编码器使用H.264编码

[cpp]  view plain  copy
 
  1. //output initialize  
  2.     avformat_alloc_output_context2(&ofmt_ctx, NULL, "flv", out_path);  
  3.     //output encoder initialize  
  4.     pCodec = avcodec_find_encoder(AV_CODEC_ID_H264);  
  5.     if (!pCodec){  
  6.         printf("Can not find encoder! (没有找到合适的编码器!)\n");  
  7.         return -1;  
  8.     }  
  9.     pCodecCtx=avcodec_alloc_context3(pCodec);  
  10.     pCodecCtx->pix_fmt = PIX_FMT_YUV420P;  
  11.     pCodecCtx->width = ifmt_ctx->streams[videoindex]->codec->width;  
  12.     pCodecCtx->height = ifmt_ctx->streams[videoindex]->codec->height;  
  13.     pCodecCtx->time_base.num = 1;  
  14.     pCodecCtx->time_base.den = 25;  
  15.     pCodecCtx->bit_rate = 400000;  
  16.     pCodecCtx->gop_size = 250;  
  17.     /* Some formats,for example,flv, want stream headers to be separate. */  
  18.     if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)  
  19.         pCodecCtx->flags |= CODEC_FLAG_GLOBAL_HEADER;  
  20.   
  21.     //H264 codec param  
  22.     //pCodecCtx->me_range = 16;  
  23.     //pCodecCtx->max_qdiff = 4;  
  24.     //pCodecCtx->qcompress = 0.6;  
  25.     pCodecCtx->qmin = 10;  
  26.     pCodecCtx->qmax = 51;  
  27.     //Optional Param  
  28.     pCodecCtx->max_b_frames = 3;  
  29.     // Set H264 preset and tune  
  30.     AVDictionary *param = 0;  
  31.     av_dict_set(¶m, "preset", "fast", 0);  
  32.     av_dict_set(¶m, "tune", "zerolatency", 0);  
  33.   
  34.     if (avcodec_open2(pCodecCtx, pCodec,¶m) < 0){  
  35.         printf("Failed to open encoder! (编码器打开失败!)\n");  
  36.         return -1;  
  37.     }  
  38.   
  39.     //Add a new stream to output,should be called by the user before avformat_write_header() for muxing  
  40.     video_st = avformat_new_stream(ofmt_ctx, pCodec);  
  41.     if (video_st == NULL){  
  42.         return -1;  
  43.     }  
  44.     video_st->time_base.num = 1;  
  45.     video_st->time_base.den = 25;  
  46.     video_st->codec = pCodecCtx;  
  47.   
  48.     //Open output URL,set before avformat_write_header() for muxing  
  49.     if (avio_open(&ofmt_ctx->pb,out_path, AVIO_FLAG_READ_WRITE) < 0){  
  50.     printf("Failed to open output file! (输出文件打开失败!)\n");  
  51.     return -1;  
  52.     }  
  53.   
  54.     //Show some Information  
  55.     av_dump_format(ofmt_ctx, 0, out_path, 1);  
  56.   
  57.     //Write File Header  
  58.     avformat_write_header(ofmt_ctx,NULL);  


完成输入和输出的初始化以后,就能够正式开始解码和编码并推流的流程了,这里要注意,摄像头数据每每是RGB格式的,须要将其转换为YUV420P格式,因此要先作以下的准备工做spa

[cpp]  view plain  copy
 
  1. //prepare before decode and encode  
  2.     dec_pkt = (AVPacket *)av_malloc(sizeof(AVPacket));  
  3.     //enc_pkt = (AVPacket *)av_malloc(sizeof(AVPacket));  
  4.     //camera data has a pix fmt of RGB,convert it to YUV420  
  5.     img_convert_ctx = sws_getContext(ifmt_ctx->streams[videoindex]->codec->width, ifmt_ctx->streams[videoindex]->codec->height,   
  6.         ifmt_ctx->streams[videoindex]->codec->pix_fmt, pCodecCtx->width, pCodecCtx->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);  
  7.     pFrameYUV = avcodec_alloc_frame();  
  8.     uint8_t *out_buffer = (uint8_t *)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));  
  9.     avpicture_fill((AVPicture *)pFrameYUV, out_buffer, PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);  


下面就能够正式开始解码、编码和推流了.net

[cpp]  view plain  copy
 
  1. //start decode and encode  
  2.     int64_t start_time=av_gettime();  
  3.     while (av_read_frame(ifmt_ctx, dec_pkt) >= 0){     
  4.         if (exit_thread)  
  5.             break;  
  6.         av_log(NULL, AV_LOG_DEBUG, "Going to reencode the frame\n");  
  7.         pframe = av_frame_alloc();  
  8.         if (!pframe) {  
  9.             ret = AVERROR(ENOMEM);  
  10.             return -1;  
  11.         }  
  12.         //av_packet_rescale_ts(dec_pkt, ifmt_ctx->streams[dec_pkt->stream_index]->time_base,  
  13.         //  ifmt_ctx->streams[dec_pkt->stream_index]->codec->time_base);  
  14.         ret = avcodec_decode_video2(ifmt_ctx->streams[dec_pkt->stream_index]->codec, pframe,  
  15.             &dec_got_frame, dec_pkt);  
  16.         if (ret < 0) {  
  17.             av_frame_free(&pframe);  
  18.             av_log(NULL, AV_LOG_ERROR, "Decoding failed\n");  
  19.             break;  
  20.         }  
  21.         if (dec_got_frame){  
  22.             sws_scale(img_convert_ctx, (const uint8_t* const*)pframe->data, pframe->linesize, 0, pCodecCtx->height, pFrameYUV->data, pFrameYUV->linesize);     
  23.   
  24.             enc_pkt.data = NULL;  
  25.             enc_pkt.size = 0;  
  26.             av_init_packet(&enc_pkt);  
  27.             ret = avcodec_encode_video2(pCodecCtx, &enc_pkt, pFrameYUV, &enc_got_frame);  
  28.             av_frame_free(&pframe);  
  29.             if (enc_got_frame == 1){  
  30.                 //printf("Succeed to encode frame: %5d\tsize:%5d\n", framecnt, enc_pkt.size);  
  31.                 framecnt++;   
  32.                 enc_pkt.stream_index = video_st->index;  
  33.   
  34.                 //Write PTS  
  35.                 AVRational time_base = ofmt_ctx->streams[videoindex]->time_base;//{ 1, 1000 };  
  36.                 AVRational r_framerate1 = ifmt_ctx->streams[videoindex]->r_frame_rate;// { 50, 2 };  
  37.                 AVRational time_base_q = { 1, AV_TIME_BASE };  
  38.                 //Duration between 2 frames (us)  
  39.                 int64_t calc_duration = (double)(AV_TIME_BASE)*(1 / av_q2d(r_framerate1));  //内部时间戳  
  40.                 //Parameters  
  41.                 //enc_pkt.pts = (double)(framecnt*calc_duration)*(double)(av_q2d(time_base_q)) / (double)(av_q2d(time_base));  
  42.                 enc_pkt.pts = av_rescale_q(framecnt*calc_duration, time_base_q, time_base);  
  43.                 enc_pkt.dts = enc_pkt.pts;  
  44.                 enc_pkt.duration = av_rescale_q(calc_duration, time_base_q, time_base); //(double)(calc_duration)*(double)(av_q2d(time_base_q)) / (double)(av_q2d(time_base));  
  45.                 enc_pkt.pos = -1;  
  46.                   
  47.                 //Delay  
  48.                 int64_t pts_time = av_rescale_q(enc_pkt.dts, time_base, time_base_q);  
  49.                 int64_t now_time = av_gettime() - start_time;  
  50.                 if (pts_time > now_time)  
  51.                     av_usleep(pts_time - now_time);  
  52.   
  53.                 ret = av_interleaved_write_frame(ofmt_ctx, &enc_pkt);  
  54.                 av_free_packet(&enc_pkt);  
  55.             }  
  56.         }  
  57.         else {  
  58.             av_frame_free(&pframe);  
  59.         }  
  60.         av_free_packet(dec_pkt);  
  61.     }  


解码部分比较简单,编码部分须要本身计算PTS、DTS,比较复杂。这里经过帧率计算PTS和DTS
首先经过帧率计算每两帧之间的时间间隔,可是要换算命令行