本文的示例将实现:读取PC摄像头视频数据并以RTMP协议发送为直播流。示例包含了

1、ffmpeg的libavdevice的使用

2、视频解码、编码、推流的基本流程

具有较强的综合性。

要使用libavdevice的相关函数,首先需要注册相关组件

  1. avdevice_register_all();

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

  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,这样与在命令行中输入下列命令有相同的效果

  1. ffmpeg -list_devices true -f dshow -i dummy

以上语句得到的结果如下

这里我的电脑上只有一个虚拟摄像头软件虚拟出来的几个dshow设备,没有音频设备,所以有如上的结果。

需要说明的是,avdevice有一个avdevice_list_devices函数可以枚举系统的采集设备,包括设备名和设备描述,非常适合用于让用户选择要使用的设备,但是不支持dshow设备,所以这里没有使用它。

下一步就可以像打开普通文件一样将上面的具体设备名作为输入打开,并进行相应的初始化设置,如下

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

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

  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. //H264 codec param
  21. //pCodecCtx->me_range = 16;
  22. //pCodecCtx->max_qdiff = 4;
  23. //pCodecCtx->qcompress = 0.6;
  24. pCodecCtx->qmin = 10;
  25. pCodecCtx->qmax = 51;
  26. //Optional Param
  27. pCodecCtx->max_b_frames = 3;
  28. // Set H264 preset and tune
  29. AVDictionary *param = 0;
  30. av_dict_set(&param, "preset", "fast", 0);
  31. av_dict_set(&param, "tune", "zerolatency", 0);
  32. if (avcodec_open2(pCodecCtx, pCodec,&param) < 0){
  33. printf("Failed to open encoder! (编码器打开失败!)\n");
  34. return -1;
  35. }
  36. //Add a new stream to output,should be called by the user before avformat_write_header() for muxing
  37. video_st = avformat_new_stream(ofmt_ctx, pCodec);
  38. if (video_st == NULL){
  39. return -1;
  40. }
  41. video_st->time_base.num = 1;
  42. video_st->time_base.den = 25;
  43. video_st->codec = pCodecCtx;
  44. //Open output URL,set before avformat_write_header() for muxing
  45. if (avio_open(&ofmt_ctx->pb,out_path, AVIO_FLAG_READ_WRITE) < 0){
  46. printf("Failed to open output file! (输出文件打开失败!)\n");
  47. return -1;
  48. }
  49. //Show some Information
  50. av_dump_format(ofmt_ctx, 0, out_path, 1);
  51. //Write File Header
  52. avformat_write_header(ofmt_ctx,NULL);

完成输入和输出的初始化之后,就可以正式开始解码和编码并推流的流程了,这里要注意,摄像头数据往往是RGB格式的,需要将其转换为YUV420P格式,所以要先做如下的准备工作

  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);

下面就可以正式开始解码、编码和推流了

  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. enc_pkt.data = NULL;
  24. enc_pkt.size = 0;
  25. av_init_packet(&enc_pkt);
  26. ret = avcodec_encode_video2(pCodecCtx, &enc_pkt, pFrameYUV, &enc_got_frame);
  27. av_frame_free(&pframe);
  28. if (enc_got_frame == 1){
  29. //printf("Succeed to encode frame: %5d\tsize:%5d\n", framecnt, enc_pkt.size);
  30. framecnt++;
  31. enc_pkt.stream_index = video_st->index;
  32. //Write PTS
  33. AVRational time_base = ofmt_ctx->streams[videoindex]->time_base;//{ 1, 1000 };
  34. AVRational r_framerate1 = ifmt_ctx->streams[videoindex]->r_frame_rate;// { 50, 2 };
  35. AVRational time_base_q = { 1, AV_TIME_BASE };
  36. //Duration between 2 frames (us)
  37. int64_t calc_duration = (double)(AV_TIME_BASE)*(1 / av_q2d(r_framerate1));  //内部时间戳
  38. //Parameters
  39. //enc_pkt.pts = (double)(framecnt*calc_duration)*(double)(av_q2d(time_base_q)) / (double)(av_q2d(time_base));
  40. enc_pkt.pts = av_rescale_q(framecnt*calc_duration, time_base_q, time_base);
  41. enc_pkt.dts = enc_pkt.pts;
  42. 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));
  43. enc_pkt.pos = -1;
  44. //Delay
  45. int64_t pts_time = av_rescale_q(enc_pkt.dts, time_base, time_base_q);
  46. int64_t now_time = av_gettime() - start_time;
  47. if (pts_time > now_time)
  48. av_usleep(pts_time - now_time);
  49. ret = av_interleaved_write_frame(ofmt_ctx, &enc_pkt);
  50. av_free_packet(&enc_pkt);
  51. }
  52. }
  53. else {
  54. av_frame_free(&pframe);
  55. }
  56. av_free_packet(dec_pkt);
  57. }

解码部分比较简单,编码部分需要自己计算PTS、DTS,比较复杂。这里通过帧率计算PTS和DTS

首先通过帧率计算每两帧之间的时间间隔,但是要换算为ffmpeg内部的时间基表示的值。所谓ffmpeg内部的时间基即AV_TIME_BASE,定义为

  1. #define         AV_TIME_BASE   1000000

任何以秒为单位的时间值都通过下式转换为ffmpeg内部时间基表示的时间值,其实就是转换为了微秒

  1. timestamp=AV_TIME_BASE*time(s)

所以有

  1. //Duration between 2 frames (us)
  2. int64_t calc_duration = (double)(AV_TIME_BASE)*(1 / av_q2d(r_framerate1));  //内部时间戳

而enc_pkt因为是要写入最后的输出码流的,它的PTS、DTS应该是以ofmt_ctx->streams[videoindex]->time_base为时间基来表示的,时间基之间的转换用下式

  1. enc_pkt.pts = av_rescale_q(framecnt*calc_duration, time_base_q, time_base);

其实就是

  1. enc_pkt.pts = (double)(framecnt*calc_duration)*(double)(av_q2d(time_base_q)) / (double)(av_q2d(time_base));

非常简单的数学转换。

还有一点,因为转码流程可能比实际的播放快很多,为保持流畅的播放,要判断DTS和当前真实时间,并进行相应的延时操作,如下

  1. //Delay
  2. int64_t pts_time = av_rescale_q(enc_pkt.dts, time_base, time_base_q);
  3. int64_t now_time = av_gettime() - start_time;
  4. if (pts_time > now_time)
  5. av_usleep(pts_time - now_time);

这里正好与之前相反,要将ofmt_ctx->streams[videoindex]->time_base时间基转换为ffmpeg内部时间基,因为av_gettime获得的就是以微秒为单位的时间

总体流程完毕之后,还剩下最后的flush encoder操作,输出之前存储在缓冲区内的数据

  1. //Flush Encoder
  2. ret = flush_encoder(ifmt_ctx,ofmt_ctx,0,framecnt);
  3. if (ret < 0) {
  4. printf("Flushing encoder failed\n");
  5. return -1;
  6. }
  7. //Write file trailer
  8. av_write_trailer(ofmt_ctx);
  9. //Clean
  10. if (video_st)
  11. avcodec_close(video_st->codec);
  12. av_free(out_buffer);
  13. avio_close(ofmt_ctx->pb);
  14. avformat_free_context(ifmt_ctx);
  15. avformat_free_context(ofmt_ctx);

flush_encoder的内容如下

  1. int flush_encoder(AVFormatContext *ifmt_ctx, AVFormatContext *ofmt_ctx, unsigned int stream_index, int framecnt){
  2. int ret;
  3. int got_frame;
  4. AVPacket enc_pkt;
  5. if (!(ofmt_ctx->streams[stream_index]->codec->codec->capabilities &
  6. CODEC_CAP_DELAY))
  7. return 0;
  8. while (1) {
  9. enc_pkt.data = NULL;
  10. enc_pkt.size = 0;
  11. av_init_packet(&enc_pkt);
  12. ret = avcodec_encode_video2 (ofmt_ctx->streams[stream_index]->codec, &enc_pkt,
  13. NULL, &got_frame);
  14. av_frame_free(NULL);
  15. if (ret < 0)
  16. break;
  17. if (!got_frame){
  18. ret=0;
  19. break;
  20. }
  21. printf("Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n",enc_pkt.size);
  22. //Write PTS
  23. AVRational time_base = ofmt_ctx->streams[stream_index]->time_base;//{ 1, 1000 };
  24. AVRational r_framerate1 = ifmt_ctx->streams[stream_index]->r_frame_rate;// { 50, 2 };
  25. AVRational time_base_q = { 1, AV_TIME_BASE };
  26. //Duration between 2 frames (us)
  27. int64_t calc_duration = (double)(AV_TIME_BASE)*(1 / av_q2d(r_framerate1));  //内部时间戳
  28. //Parameters
  29. enc_pkt.pts = av_rescale_q(framecnt*calc_duration, time_base_q, time_base);
  30. enc_pkt.dts = enc_pkt.pts;
  31. enc_pkt.duration = av_rescale_q(calc_duration, time_base_q, time_base);
  32. /* copy packet*/
  33. //转换PTS/DTS(Convert PTS/DTS)
  34. enc_pkt.pos = -1;
  35. framecnt++;
  36. ofmt_ctx->duration=enc_pkt.duration * framecnt;
  37. /* mux encoded frame */
  38. ret = av_interleaved_write_frame(ofmt_ctx, &enc_pkt);
  39. if (ret < 0)
  40. break;
  41. }
  42. return ret;
  43. }

可以看到基本上就是把编码流程重复了一遍

至此,就实现了摄像头数据的直播。

当然还可以使用多线程来实现“按下回车键停止播放”这样的控制功能。

本工程源代码

from:https://blog.csdn.net/nonmarking/article/details/48022387

ffmpeg综合应用示例(一)——摄像头直播的更多相关文章

  1. ffmpeg综合应用示例(三)——安卓手机摄像头编码

    本文的示例将实现:读取安卓手机摄像头数据并使用H.264编码格式实时编码保存为flv文件.示例包含了 1.编译适用于安卓平台的ffmpeg库 2.在java中通过JNI使用ffmpeg 3.读取安卓摄 ...

  2. WebRTC VideoEngine综合应用示例(一)——视频通话的基本流程(转)

    本系列目前共三篇文章,后续还会更新 WebRTC VideoEngine综合应用示例(一)——视频通话的基本流程 WebRTC VideoEngine综合应用示例(二)——集成OPENH264编解码器 ...

  3. 全互联结构DVPN综合配置示例

    以下内容摘自正在全面热销的最新网络设备图书“豪华四件套”之一<H3C路由器配置与管理完全手册>(第二版)(其余三本分别是:<Cisco交换机配置与管理完全手册>(第二版).&l ...

  4. PIE SDK组件式开发综合运用示例

    1. 功能概述 关于PIE SDK的功能开发,在我们的博客上已经分门别类的进行了展示,点击PIESat博客就可以访问,为了初学者入门,本章节将对从PIE SDK组件式二次开发如何搭建界面.如何综合开发 ...

  5. Windows10环境下 Nginx+ffmpeg自搭服务器制作RTMP直播流

    Windows10环境下 Nginx+ffmpeg自搭服务器制作RTMP直播流学习笔记 所需条件: nginx-rtmp-module(带rtmp模块) ,链接:https://link.jiansh ...

  6. 项目实战:Qt+Ffmpeg+OpenCV相机程序(打开摄像头、支持多种摄像头、分辨率调整、翻转、旋转、亮度调整、拍照、录像、回放图片、回放录像)

    若该文为原创文章,未经允许不得转载原博主博客地址:https://blog.csdn.net/qq21497936原博主博客导航:https://blog.csdn.net/qq21497936/ar ...

  7. Django笔记&教程 5-3 综合使用示例

    Django 自学笔记兼学习教程第5章第3节--综合使用示例 点击查看教程总目录 1 - 生成学号场景 场景描述: 教务管理系统中,学生注册账号,学生选择年级后,生成唯一学号. 细节分析: 学生学号由 ...

  8. [转载] ffmpeg超详细综合教程——摄像头直播

    本文的示例将实现:读取PC摄像头视频数据并以RTMP协议发送为直播流.示例包含了 1.ffmpeg的libavdevice的使用 2.视频解码.编码.推流的基本流程 具有较强的综合性. 要使用liba ...

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

    本文的示例将实现:读取PC摄像头视频数据并以RTMP协议发送为直播流.示例包含了1.ffmpeg的libavdevice的使用2.视频解码.编码.推流的基本流程具有较强的综合性.要使用libavdev ...

随机推荐

  1. 《Computational Statistics with Matlab》硬译2

    T=; sigma=; thetamin=-;thetamax=; theta=zeros(,T); seed=;rand('state',seed);randn('state',seed); the ...

  2. BZOJ4455/UOJ185 [Zjoi2016]小星星

    本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000 作者博客:http://www.cnblogs.com/ljh2000-jump/ ...

  3. 用Java编程计算猴子吃桃问题

    猴子吃桃问题:猴子吃桃子问题:猴子第一天摘下N个桃子,当时就吃了一半,还不过瘾,就又吃了一个.第二天又将剩下的桃子吃掉一半,又多吃了一个.以后每天都吃前一天剩下的一半零一个.到第10天在想吃的时候就剩 ...

  4. 解析CEPH: 存储引擎实现之一 filestore

    Ceph作为一个高可用和强一致性的软件定义存储实现,去使用它非常重要的就是了解其内部的IO路径和存储实现.这篇文章主要介绍在IO路径中最底层的ObjectStore的实现之一FileStore. Ob ...

  5. flask学习(一):环境的安装

    一. 安装python2.7 从python官网下载python2.7的版本 双击python2.7,然后选择安装路径,一直下一步就可以了 设置环境变量,把python和pip的安装路径添加到PATH ...

  6. Isilon

    Isilon编辑 本词条缺少信息栏,补充相关内容使词条更完整,还能快速升级,赶紧来编辑吧! 美国Isilon公司是全球群集存储系统的主要供应商,是该领域的领导者.总部位于美国华盛顿州的西雅图.创建于2 ...

  7. oracle数据库查看用户相关语句

    1.查看所有用户:   select * from dba_users;   select * from all_users;   select * from user_users;   2.查看用户 ...

  8. 【spark】示例:连接操作

    我们有这样两个文件 任务:找出用户评分平均值大于4的电影. 我们看两个文件结果,第一个文件有电影的ID和名字,第二个文件有电影的ID和所有用户的评分 对于任务结果所需要的数据为电影ID,电影名字,平均 ...

  9. 【scala】匿名函数和闭包

    函数的类型和值 Scala是一种纯面向对象的语言,每个值都是对象.Java是一种不全面向对象的语言. Scala也是一种函数式语言,其函数也能当成值来使用.Java则是指令试编程. 但是Scala同时 ...

  10. 【PAT-L2-020】功夫传人

    链接:https://www.patest.cn/contests/gplt/L2-020 一门武功能否传承久远并被发扬光大,是要看缘分的.一般来说,师傅传授给徒弟的武功总要打个折扣,于是越往后传,弟 ...