本篇将是合成mp4前的最后一次准备工作,在压缩AAC之前要叨逼叨逼一番。。。。
在此之前请认真阅读如下两篇文章
FFmpeg学习4:音频格式转换
FFMPEG实现音频重采样
PCM存储格式大体分为两种Planner和Packed
我们以双声道为例,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捕获游戏,硬件压缩等等。
下一篇将对合流展开介绍。