说明
记录一下Qt环境捕捉麦克风原始pcm数据,通过ffmpeg编程成AAC音频的核心代码。此部分代码摘抄自个人的RTP实时音视频传输项目,实现了子线程采集pcm并编码为AVPacket的功能。
代码
void AudioCapturer::run()
{
if(rtpSender == nullptr)
{
qDebug() << "外部RTP发送者指针无效";
return;
}
//设置Qt音频录制参数
int sampleRate = 44100;
int channels = 2;
int sampleByte = 2;
AVSampleFormat inSampleFmt = AV_SAMPLE_FMT_S16;
QAudioFormat recordFmt;
recordFmt.setSampleRate(sampleRate);
recordFmt.setChannelCount(channels);
recordFmt.setSampleSize(sampleByte * 8);
recordFmt.setCodec("audio/pcm");
recordFmt.setByteOrder(QAudioFormat::LittleEndian);
recordFmt.setSampleType(QAudioFormat::UnSignedInt);
QAudioDeviceInfo info = QAudioDeviceInfo::defaultInputDevice();
if (!info.isFormatSupported(recordFmt))
{
qDebug() << "Audio format not support!";
recordFmt = info.nearestFormat(recordFmt);
}
QAudioInput *audioInput = new QAudioInput(recordFmt);
//ffmpeg -h encoder=aac 查询自带编码器仅支持AV_SAMPLE_FMT_FLTP 大多数AAC编码器都采用平面布局数据格式 可以提高数据访问效率和缓存命中率 加快编码效率
AVSampleFormat outSampleFmt = AV_SAMPLE_FMT_FLTP;
rtpSender->info.sampleRate = sampleRate;
rtpSender->info.format = outSampleFmt;
rtpSender->info.channel = channels;
rtpSender->info.hasAudio = 1;
rtpSender->setAudio = true;
//设置重采样
SwrContext *audioSwrCtx = swr_alloc_set_opts(NULL, av_get_default_channel_layout(channels), outSampleFmt, sampleRate,
av_get_default_channel_layout(channels), inSampleFmt, sampleRate, 0, NULL);
if (!audioSwrCtx)
{
qDebug() << "swr_alloc_set_opts failed!";
return;
}
int ret = swr_init(audioSwrCtx);
if (ret != 0)
{
qDebug() << "swr_init error" << getAVError(ret);
swr_free(&audioSwrCtx);
return;
}
AVFrame *swrFrame = av_frame_alloc();
swrFrame->format = outSampleFmt;
swrFrame->channels = channels;
swrFrame->channel_layout = av_get_default_channel_layout(channels);
swrFrame->nb_samples = 1024;
ret = av_frame_get_buffer(swrFrame, 0);
if (ret != 0)
{
qDebug() << "av_frame_get_buffer audio" << getAVError(ret);
return;
}
ret = av_frame_make_writable(swrFrame);
if (ret != 0)
{
qDebug() << "av_frame_is_writable audio" << getAVError(ret);
return;
}
//初始化音频编码器相关
AVCodec *audioEncoder = avcodec_find_encoder(AV_CODEC_ID_AAC);
if (!audioEncoder)
{
qDebug() << "avcodec_find_encoder AV_CODEC_ID_AAC failed!";
return;
}
AVCodecContext *audioCodecCtx = avcodec_alloc_context3(audioEncoder);
if (!audioCodecCtx)
{
qDebug() << "avcodec_alloc_context3 AV_CODEC_ID_AAC failed!";
return;
}
audioCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
audioCodecCtx->bit_rate = 64*1024;
audioCodecCtx->sample_rate = sampleRate;
audioCodecCtx->sample_fmt = outSampleFmt;
audioCodecCtx->channels = channels;
audioCodecCtx->channel_layout = av_get_default_channel_layout(channels);
audioCodecCtx->frame_size = 1024;
AVDictionary *dict = NULL;
av_dict_set(&dict, "tune", "zerolatency", 0);
//打开音频编码器
ret = avcodec_open2(audioCodecCtx, audioEncoder, NULL);
if (dict)
{
av_dict_free(&dict);
dict = NULL;
}
if (ret != 0)
{
qDebug() << "avcodec_open2 audio error" << getAVError(ret);
return;
}
//一帧pcm原始数据的字节数
int pcmSize = av_get_bytes_per_sample((AVSampleFormat)inSampleFmt) * channels * audioCodecCtx->frame_size;
char *pcmBuf = new char[pcmSize];
//采用实时时间戳
int64_t startTime = av_gettime();
//音频数据开始捕获
QIODevice *audioIO = audioInput->start();
qDebug() << "init AudioCapturer Sucess" << sampleRate << channels << outSampleFmt;
while(isRun)
{
if (audioInput->bytesReady() >= pcmSize)
{
//捕获一帧pcm原始数据
int size = 0;
while (size != pcmSize)
{
int len = audioIO->read(pcmBuf + size, pcmSize - size);
if (len < 0)
break;
size += len;
}
//重采样后进行编码处理
const uint8_t *indata[AV_NUM_DATA_POINTERS] = {0};
indata[0] = (uint8_t *)pcmBuf;
swr_convert(audioSwrCtx, swrFrame->data, swrFrame->nb_samples, indata, swrFrame->nb_samples);
//添加时间戳 实时时间戳方式
int64_t audioPts = av_rescale_q(av_gettime() - startTime, AVRational{ 1, AV_TIME_BASE }, audioCodecCtx->time_base);
swrFrame->pts = audioPts;
//进行音频编码得到编码数据AVPacket
int ret = avcodec_send_frame(audioCodecCtx, swrFrame);
if (ret != 0)
continue;
while(1)
{
AVPacket *packet = av_packet_alloc();
ret = avcodec_receive_packet(audioCodecCtx, packet);
if (ret != 0)
{
av_packet_free(&packet);
break;
}
ReadyPacket *readyPacket = new ReadyPacket();
readyPacket->type = PacketAudio;
readyPacket->packet = packet;
rtpSender->writePacket(readyPacket);
}
}
QThread::msleep(1);
}
//释放资源
audioInput->stop();
audioIO->close();
delete audioInput;
avcodec_free_context(&audioCodecCtx);
audioCodecCtx = nullptr;
swr_free(&audioSwrCtx);
audioSwrCtx = nullptr;
av_frame_free(&swrFrame);
swrFrame = nullptr;
delete[] pcmBuf;
pcmBuf = nullptr;
}