大致流程

如何用摄像机连接电脑录制(捕获并保存摄像头和麦克风数据)(1)

将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(¶m1, "video_size", "640x480", 0); //宽高 av_dict_set(¶m1, "framerate", "30", 0); //帧率 av_dict_set(¶m1, "rtbufsize", "30000000", 0); //缓冲区大小 av->v_ret = avformat_open_input(&av->pVFmtCtx, av->video_device_name, av->pAVInFmt, ¶m1); .......

视频编码器参数

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参数:表示画质级别,比如

音频编码器参数

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码率,在一定范围内,码率越大,视频质量越好,文件大小也越大;

C 音视频开发学习资料:点击领取→音视频开发(资料文档 视频教程 面试题)(FFmpeg WebRTC RTMP RTSP HLS RTP)

数据包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);

,