• 音视频录制器—捕获并保存摄像头和麦克风数据


    ------------------------------------全系列文章目录------------------------------------

    大致流程

    在这里插入图片描述

    将dshow设备作为数据输入源
    • ffmpeg中可将dshow设备作为数据输入源,其操作与操作文件作为数据源大同小异,以视频输入设备举例:

      /*打开视频设备伪代码*/
      //因为字符标准的问题, vs平台下,设备有中文名需要转换为utf8才能被ffmpeg识别到
      char *dup_wchar_to_utf8(wchar_t *w)
      {
      	char *s = NULL;
      	int l = WideCharToMultiByte(CP_UTF8, 0, w, -1, 0, 0, 0, 0);
      	s = (char *) av_malloc(l);
      	if (s)
      		WideCharToMultiByte(CP_UTF8, 0, w, -1, s, l, 0, 0);
      	return s;
      }
      	......
          av->video_device_name = dup_wchar_to_utf8(L"video=摄像头");
          /* Open an input stream and read the header -- a specific input format */
      	av->pAVInFmt  = av_find_input_format("dshow");
      	/*open input device of video stream*/
      	AVDictionary	*param1 = NULL;
      	av_dict_set(&param1, "video_size", "640x480", 0);	//宽高
      	av_dict_set(&param1, "framerate", "30", 0);			//帧率
      	av_dict_set(&param1, "rtbufsize", "30000000", 0);	//缓冲区大小
      	av->v_ret = avformat_open_input(&av->pVFmtCtx, av->video_device_name, av->pAVInFmt, &param1);
      	.......
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
    视频编码器参数
    • codec_id编码器ID,如AV_CODEC_ID_H264;

    • 编码器输入数据的像素格式pix_fmt,如AV_PIX_FMT_YUV420P;

    • 编码器输入数据的宽高,帧率;

    • bit_rate码率,在一定范围内,码率越大,视频质量越好,文件大小也越大;

    • gop_size,一组数据中的帧数量;

    • max_b_frames,非B帧间B帧的最大数量;

    • qmin,qmax,图像质量的量化参数,范围为0~51;

    • 若是H264编码器,还可以设置相关参数,如

      • preset参数:调节编码速度和质量的平衡,有veryfast、fast、medium、slow等这10个选项,从快到慢,相对应于图像质量从差到好;

      • tune参数:主要配合视频类型和视觉优化的参数,比如

        • zerolatency:零延迟,用在需要非常低的延迟的情况下

        • film: 电影类型;

        • animation: 动画类型;

      • Profile参数:表示画质级别,比如

        • Baseline Profile 提供I/P帧,仅支持progressive(逐行扫描)和CAVLC;

        • Main Profile 提供I/P/B帧,支持progressive(逐行扫描)和interlaced(隔行扫描),提供CAVLC或CABAC;

    音频编码器参数
    • codec_id编码器ID,如AV_CODEC_ID_AAC;

    • sample_fmt输入数据的采样格式,如AV_SAMPLE_FMT_S16;

    • sample_rate输入数据的采样率,如44100,48000;

    • channels和channel_layout,输入数据的通道数和通道布局,如双通道,AV_CH_LAYOUT_STEREO布局;

    • bit_rate码率,在一定范围内,码率越大,视频质量越好,文件大小也越大;

    数据包pts设置
    • 编码前原始数据AVFrame的pts,可以通过av_gettime() 来得到,即开始捕获视频帧/音频帧时获取一个基准时间,随后基于这个基准时间获取当前帧的pts;

    • 需要注意的是,从麦克风获取的音频数据包是由几帧数据组成,因此对于每一帧的pts,还需要根据获取到数据包的总采样数和每帧采样数,基于基准时间去获取当前帧的pts;

    • 因为当前得到的pts不是基于输出文件数据流的时间基,所以需要通过av_rescale_q_rnd()来转换时间基,包括编码器输出数据包的dts和duration;

      /*视频帧pts获取的伪代码*/
      av->time_base        = av_gettime();
      while (!av->vexit) {
          ......
          av->pVFrameYUV->pts = av_gettime() - av->time_base;
          ret = avcodec_encode_video2(enc->pCodecCtx, &enc->pkt, av->pVFrameYUV, &enc->flag);
          ......
          enc->pkt.pts = av_rescale_q_rnd(enc->pkt.pts, enc->stream->time_base, enc->pCodecCtx->time_base, 
                                         (enum AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
          enc->pkt.dts = av_rescale_q_rnd(enc->pkt.dts, enc->stream->time_base, enc->pCodecCtx->time_base, 
                                         (enum AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
          enc->pkt.duration = av_rescale_q_rnd(enc->pkt.duration, enc->stream->time_base, enc->pCodecCtx->time_base, 
                                         (enum AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
          av_interleaved_write_frame(enc->pFmtCtx, &enc->pkt);
          .......
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      /*音频帧pts获取的伪代码*/
      av->time_base        = av_gettime();
      while (!av->vexit) {
          ......
          int nb_sample     = av->pAFramePCM->nb_samples;    //one time received_frame nb_samples
          int frame_nbs     = enc->pCodecCtx->frame_size;    //the frame nb_samples
          int64_t incra_pts = (int64_t)(1000000 * (int64_t)enc->pCodecCtx->frame_size) / av->pAFramePCM->nb_samples;
          int64_t pts_basic = av_gettime() - av->time_base;
          int64_t last_pf_pts    = 0;
          ......
          do {
          ......
              if (pf->pts < last_pf_pts) {
                  pf->pts = last_pf_pts + incra_pts;
              }
              last_pf_pts      =  pf->pts;
          ......
              ret = avcodec_encode_audio2(enc->pCodecCtx, &enc->pkt, pf, &enc->flag);
          ......
              av->acnt ++;
              enc->pkt.pts = av_rescale_q_rnd(enc->pkt.pts, enc->stream->time_base, enc->pCodecCtx->time_base, 
                                                                  (enum AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
              enc->pkt.dts = av_rescale_q_rnd(enc->pkt.dts, enc->stream->time_base, enc->pCodecCtx->time_base, 
                                                                  (enum AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
              enc->pkt.duration = av_rescale_q_rnd(enc->pkt.duration, enc->stream->time_base, enc->pCodecCtx->time_base, 
                                                                  (enum AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
              av_interleaved_write_frame(enc->pFmtCtx, &enc->pkt);
              ......
          } while (nb_sample > 0);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
    注意的地方
    • 音频编码和视频编码的速度不一致,导致结束录制时留存在缓冲区的音频帧和视频帧数量不一致,就会导致音画不一致,此时有两种做法

      • 将缓冲区的数据全部取出来,进行编码打包;

      • 以较少时间的数据流为基准,裁剪另一数据流;

    相关API函数 — 与之前文章重复的函数未罗列
    • av_find_input_format:根据输入格式的短名称查找 AVInputFormat。

      AVInputFormat *av_find_input_format(const char *short_name);
      
      • 1
    • av_dict_set:在AVDictionary中设置给定条目指定的值,覆盖现有条目的值。

      int av_dict_set(AVDictionary **pm, const char *key, 
                      const char *value, int flags);
      
      • 1
      • 2
      • pm:指向字典结构指针的指针。如果 *pm 为 NULL,则分配一个字典结构并放入 *pm。

      • key:要添加到 *pm 的入口键。

      • value:要设置的入口键的值,若为NULL,则删除原值。

      • 返回值:≥0成功,否则失败。

    • avcodec_encode_video2:对视频帧进行编码,从AVFrame中获取输入的原始视频数据,并将下一个输出数据包写入到AVPacket中。

      int avcodec_encode_video2(AVCodecContext *avctx, AVPacket *avpkt,
                                const AVFrame *frame, int *got_packet_ptr);
      
      • 1
      • 2
      • avctx:编解码器上下文。

      • avpkt:编码输出的AVPacket。

      • frame:包含要编码原始视频数据的AVFrame。

      • got_packet_ptr:如果输出数据包为非空,则libavcodec将此字段设置为1,如果为空,则将其设置为0。如果函数返回错误,则可以假设数据包无效,并且got_packet_ptr值为未定义,因此不使用该数据包。

      • 返回值:0表示成功,负数表示失败。

    • avcodec_encode_audio2:对音频帧进行编码,从AVFrame中获取输入的原始视频数据,并将下一个输出数据包写入到AVPacket中。

      int avcodec_encode_audio2(AVCodecContext *avctx, AVPacket *avpkt,
                                const AVFrame *frame, int *got_packet_ptr);
      
      • 1
      • 2
      • avctx:编解码器上下文。

      • avpkt:编码输出的AVPacket。

      • frame:包含要编码原始视频数据的AVFrame。

      • got_packet_ptr:如果输出数据包为非空,则libavcodec将此字段设置为1,如果为空,则将其设置为0。如果函数返回错误,则可以假设数据包无效,并且got_packet_ptr值为未定义,因此不使用该数据包。

      • 返回值:0表示成功,负数表示失败。

    • av_write_frame:将数据包写入输出媒体文件,与av_interleaved_write_frame类似,区别在于前者只能用于单一数据流,后者可以用于单一或多个数据流。

      int av_write_frame(AVFormatContext *s, AVPacket *pkt);
      
      • 1
  • 相关阅读:
    oracle数据库如何提高查询效率
    测试自动化:TPT API
    maven-resources-production:trunk-auth: java.lang.NegativeArraySizeException
    Intel 开发手册
    【企业动态】开启新征程,谱写新篇章 | 数商云喜迎乔迁
    k8s简介以及各个组件
    Abbexa丨Abbexa Actin-pan细胞ELISA试剂盒介绍
    go语言 leetcod1 两数之和
    三个课堂解决方案
    【Java】增强for循环
  • 原文地址:https://blog.csdn.net/weixin_44322983/article/details/126338300