大致流程

java录制麦克风 java获取麦克风_编码器

将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(¶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参数:表示画质级别,比如
  • 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);