FFMPEG录屏(8)---- PCM压缩AAC

本篇将是合成mp4前的最后一次准备工作,在压缩AAC之前要叨逼叨逼一番。。。。

在此之前请认真阅读如下两篇文章

FFmpeg学习4:音频格式转换
FFMPEG实现音频重采样

PCM存储格式大体分为两种PlannerPacked
我们以双声道为例,L表示左声道,R表示右声道,如下为两种格式的存储方式

Planner
LLLLLLLL… RRRRRRRR…

Packed
LRLRLRLRLR…

FFMpeg中对音频Format定义如下

enum AVSampleFormat {
    AV_SAMPLE_FMT_NONE = -1,
    AV_SAMPLE_FMT_U8,          ///< unsigned 8 bits
    AV_SAMPLE_FMT_S16,         ///< signed 16 bits
    AV_SAMPLE_FMT_S32,         ///< signed 32 bits
    AV_SAMPLE_FMT_FLT,         ///< float
    AV_SAMPLE_FMT_DBL,         ///< double

    AV_SAMPLE_FMT_U8P,         ///< unsigned 8 bits, planar
    AV_SAMPLE_FMT_S16P,        ///< signed 16 bits, planar
    AV_SAMPLE_FMT_S32P,        ///< signed 32 bits, planar
    AV_SAMPLE_FMT_FLTP,        ///< float, planar
    AV_SAMPLE_FMT_DBLP,        ///< double, planar
    AV_SAMPLE_FMT_S64,         ///< signed 64 bits
    AV_SAMPLE_FMT_S64P,        ///< signed 64 bits, planar

    AV_SAMPLE_FMT_NB           ///< Number of sample formats. DO NOT USE if linking dynamically
};

我们这里以WASAPI为例,在windows中WASAPI捕获的数据格式总是FLT,即浮点Packed格式,而新版的FFMpeg中仅仅支持FLTP格式压缩(应该是需要自行编译FFMpeg库并附加其他压缩库,如有错请指正),目前网络中的大部分博客均是老版FFMpeg所以还都在使用其他格式。
因此我们需要对PCM数据进行重新采样,请参照FFmpeg学习4:音频格式转换一文,或前往GitHub查看resample_pcm类。

初始化PCM队列
_ring_buffer = new ring_buffer<AVFrame>(1024 * 1024 * 10);
查找并初始化编码器(雷同压缩H264不详细描述了)
do {
	_encoder = avcodec_find_encoder(AV_CODEC_ID_AAC);
	if (!_encoder) {
		err = AE_FFMPEG_FIND_ENCODER_FAILED;
		break;
	}

	_encoder_ctx = avcodec_alloc_context3(_encoder);
	if (!_encoder_ctx) {
		err = AE_FFMPEG_ALLOC_CONTEXT_FAILED;
		break;
	}

	_encoder_ctx->channels = nb_channels;
	_encoder_ctx->channel_layout = av_get_default_channel_layout(nb_channels);
	_encoder_ctx->sample_rate = sample_rate;
	_encoder_ctx->sample_fmt = fmt;
	_encoder_ctx->bit_rate = bit_rate;

	_encoder_ctx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
	_encoder_ctx->time_base.den = sample_rate;
	_encoder_ctx->time_base.num = 1;
	_encoder_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

	ret = avcodec_open2(_encoder_ctx, _encoder, NULL);
	if (ret < 0) {
		err = AE_FFMPEG_OPEN_CODEC_FAILED;
		break;
	}

	_buff_size = av_samples_get_buffer_size(NULL, nb_channels, _encoder_ctx->frame_size, fmt, 1);
	_buff = (uint8_t*)av_malloc(_buff_size);

	_frame = av_frame_alloc();
	if (!_frame) {
		err = AE_FFMPEG_ALLOC_FRAME_FAILED;
		break;
	}

	_frame->channels = nb_channels;
	_frame->nb_samples = _encoder_ctx->frame_size;
	_frame->channel_layout = av_get_default_channel_layout(nb_channels);
	_frame->format = fmt;
	_frame->sample_rate = sample_rate;

	ret = avcodec_fill_audio_frame(_frame, nb_channels, fmt, _buff, _buff_size, 0);

	_inited = true;
		
} while (0);

此处需要注意的是,AAC编码中frame_size一般都是1024(Sample Count),所以要保障每次送入编码器的PCM样本数为1024,请自行缓存。

压缩函数
int encoder_aac::encode(AVFrame * frame, AVPacket * packet)
{
	int ret = avcodec_send_frame(_encoder_ctx, _frame);
	if (ret < 0) {
		return AE_FFMPEG_ENCODE_FRAME_FAILED;
	}

	while (ret >= 0) {
		av_init_packet(packet);

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

		if (ret < 0) {
			return AE_FFMPEG_READ_PACKET_FAILED;
		}

		//al_debug("AP:%lld", packet->pts);

		if (ret == 0 && _on_data)
			//do something

		av_packet_unref(packet);
	}

	return AE_NO;
}
压缩线程
void encoder_aac::encode_loop()
{
	int len = 0;
	int error = AE_NO;

	AVPacket *packet = av_packet_alloc();
	AVFrame pcm_frame;

	while (_running)
	{
		std::unique_lock<std::mutex> lock(_mutex);
		while (!_cond_notify && _running)
			_cond_var.wait_for(lock, std::chrono::milliseconds(300));

		while ((len = _ring_buffer->get(_buff, _buff_size, pcm_frame))) {

			_frame->pts = pcm_frame.pts;
			_frame->pkt_pts = pcm_frame.pkt_pts;
			_frame->pkt_dts = pcm_frame.pkt_dts;

			if ((error = encode(_frame, packet)) != AE_NO) {
				if (_on_error) 
					_on_error(error);

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

				break;
			}
		}

		_cond_notify = false;
	}

	//flush pcm data in encoder
	encode(NULL, packet);

	av_packet_free(&packet);
}

需要注意的是,encode函数采用了新版接口,PCM队列采用了c++条件变量进行通知线程进行压缩。
至此我们已经完成了录屏的前期准备工作,捕获图像和音频,当然对于图像需要更多的手段,如DXHook捕获游戏,硬件压缩等等。

下一篇将对合流展开介绍。

GitHub传送门

screen-recorder

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值