FFMPEG录屏(2)----压缩H264

在前一篇中,我们讲解了GID抓屏的整体流程,此时已经拿到了图像的原始数据BGRA,其他抓屏手段不管是Mirror Driver还是DX HOOK,得到的原始数据大都是此。

在压缩之前不妨先了解一下H264的编码基础知识。

雷神对于RGB YUV的讲解

雷神对于H264的讲解

FFMPEG视音频编解码零基础学习方法

以上两篇可能不会那么详细,具体请自行搜索相关资料。在这里要提的一点是,一些基础知识如音视频的编解码、音视频的同步原理等等最好能花时间了解一番,这些基础理论还是很有必要掌握的。

言归正传,在市面中的各类264编码器中,是无法直接对RGBA进行编码的,首先需要对齐进行转码,即转换为YUV。同样我们仍然使用ffmpeg进行转换。

初始化SwsContext

struct SwsContext *_ctx = sws_getContext(src_width,src_height,src_fmt,dst_width,dst_height,dst_fmt,SWS_BICUBIC,NULL, NULL, NULL);

其中src_fmt为桌面数据格式,在这里我们是AV_PIX_FMT_BGRA,dst_width,dst_height,dst_fmt则为转换后的图像数据宽高以及格式,在这里可以通过设置dst_width,dst_height对图像进行缩放。

计算转换后的数据大小

int _buffer_size = avpicture_get_size(dst_fmt, dst_width, dst_height);

创建一个AVFrame和buffer用以存储转换后的图像数据

uint8_t *_buffer = new uint8_t[_buffer_size];
AVFrame *_frame = av_frame_alloc();

将buffer映射到AVFrame

avpicture_fill((AVPicture*)_frame, _buffer, dst_fmt, dst_width, dst_height);

至此完成了RGBA->YUV的转换初始化,可通过如下函数进行转换

sws_scale(_ctx,(const uint8_t *const *)frame->data,frame->linesize,0, frame->height,_frame->data, _frame->linesize);

其中frame为包含桌面原始图像数据的AVframe结构

开始创建H264编码器,首先为编码器预设一些参数

AVDictionary *options = 0;

av_dict_set(&options, "preset", "superfast", 0);
av_dict_set(&options, "tune", "zerolatency", 0);

查找编码器

AVCodec *_encoder = avcodec_find_encoder(AV_CODEC_ID_H264);

为找到的编码器申请内存

AVCodecContext *_encoder_ctx = avcodec_alloc_context3(_encoder);

配置编码器各种压缩参数

_encoder_ctx->codec_id = AV_CODEC_ID_H264;
_encoder_ctx->codec_type = AVMEDIA_TYPE_VIDEO;
_encoder_ctx->pix_fmt = AV_PIX_FMT_YUV420P;
_encoder_ctx->width = pic_width;
_encoder_ctx->height = pic_height;
_encoder_ctx->time_base.num = 1;
_encoder_ctx->time_base.den = frame_rate; //时间基
_encoder_ctx->framerate = { frame_rate,1 };//帧率
_encoder_ctx->bit_rate = bit_rate;//重要,最终视频的码率
_encoder_ctx->gop_size = gop_size;//关键帧间隔
_encoder_ctx->qmin = 30;//最小压缩质量
_encoder_ctx->qmax = 35;//最大压缩质量,与最小质量一起控制压缩后的图像质量
_encoder_ctx->max_b_frames = 0;//不输出B帧,输出B帧后的时间戳控制较为繁琐
_encoder_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;//设置后通过extradata获取sps、pps

打开编码器,并在接口调用时传入一些配置参数

avcodec_open2(_encoder_ctx, _encoder, &options)

申请一个AVFrame和buffer用以接收YUV数据

AVFrame _frame = av_frame_alloc();
_frame->format = _encoder_ctx->pix_fmt;//AV_PIX_FMT_YUV420P
_frame->width = _encoder_ctx->width;
_frame->height = _encoder_ctx->height;

int _buff_size = avpicture_get_size(_encoder_ctx->pix_fmt, _encoder_ctx->width, _encoder_ctx->height);
			
uint8_t *_buff = (uint8_t*)av_malloc(_buff_size);

将buff映射至AVFrame

avpicture_fill((AVPicture*)_frame, _buff, _encoder_ctx->pix_fmt, _encoder_ctx->width, _encoder_ctx->height);

至此我们的H264编码器初始化工作已经完成,通常我们需要一个队列来缓存原始图像数据,这是因为压缩图像会有一定的时间损耗,在我这里一般在30ms左右,而我们又希望能够尽可能地达到预设的视频帧率,假设帧率为30,那么一帧图像的采集间隔将是至少30ms,如果采集后直接压缩就远远无法达到我们的预期帧率了。

所以需要新开一个线程从图像数据队列中取出图像数据进行压缩。以下是压缩线程的while循环

void encoder_264::encode_loop()
{
  int ret = 0;
  AVPacket *packet = av_packet_alloc();
  AVFrame frame;

  while (_running)
  {
    std::unique_lock<std::mutex> lock(_mutex);
    while (!_cond_notify)
	  _cond_var.wait(lock);

    while (_ring_buffer->get(_buff, _buff_size, frame)) {
	  _frame->data[0] = _buff;
	  _frame->data[1] = _buff + _y_size;
      _frame->data[2] = _buff + _y_size * 5 / 4;
	  _frame->pkt_dts = frame.pkt_dts;
	  _frame->pkt_dts = frame.pkt_dts;
	  _frame->pts = frame.pts;

	  ret = avcodec_send_frame(_encoder_ctx, _frame);
	  if (ret < 0) {
	    if (_on_error) _on_error(AE_FFMPEG_ENCODE_FRAME_FAILED);
		al_fatal("encode yuv frame failed:%d", ret);

		continue;
	  }

	  while (ret >= 0) {
	    ret = avcodec_receive_packet(_encoder_ctx, packet);
		if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
		  break;
		}

		if (ret < 0) {
		  if (_on_error) _on_error(AE_FFMPEG_READ_PACKET_FAILED);

		  al_fatal("read aac packet failed:%d", ret);
	    }

		if (_on_data) 
		  _on_data(packet);

		av_packet_unref(packet);
	  }
	}
			
	_cond_notify = false;
  }

  av_free_packet(packet);
}

这csdn的代码插入工具不好用啊。。。。缩进乱的不想理了。

至此已经完成了H264编码器的全部工作,下一篇将对系统播放和Micphone的音频数据捕获进行介绍。

 

CSDN源码下载

GitHub持续更新地址

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值