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

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

大致流程

在这里插入图片描述

将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);
    	.......
    
    
视频编码器参数
  • 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);
        .......
    }
    
    /*音频帧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);
    }
    
注意的地方
  • 音频编码和视频编码的速度不一致,导致结束录制时留存在缓冲区的音频帧和视频帧数量不一致,就会导致音画不一致,此时有两种做法

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

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

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

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

    int av_dict_set(AVDictionary **pm, const char *key, 
                    const char *value, int flags);
    
    • 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);
    
    • 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);
    
    • 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);
    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值