之前找到一个demo,是使用CoreAudio来实现音频采集的,将采集到的数据以PCM的形式存放起来,对应于FFMPEG的格式是AV_SAMPLE_FMT_S16。如果对于旧版本的FFPEG的PCM编码AAC来说,使用的PCM格式是AV_SAMPLE_FMT_S16可以满足要求,但是在新版本的FFPEG中想要将PCM编码为AAC数据,必须是需要传入PCM的格式为AV_SAMPLE_FMT_FLTP,换言之对采集到的的PCM数据需要先进行重采样由AV_SAMPLE_FMT_S16转为AV_SAMPLE_FMT_FLTP才可以进行编码。
一、具体步骤思路如下:
(1)使用CoreAudio进行PCM音频数据采集,获取得到AV_SAMPLE_FMT_S16格式的音频数据。
(2)对AV_SAMPLE_FMT_S16格式的音频数据进行重采样为AV_SAMPLE_FMT_FLTP格式的音频数据。
(3)对获取得到符合要求的音频数据进行编码即可得到AAC数据。
注意要点:
(1)使用CoreAudio获取声卡数据时需要特别注意,每一次采集数据时对应采集频率不同的声卡,每次使用API获取的数据大小是不一样的,例如本次例程中44.1KHZ对应每次获取得到的字节数为1792字节,而48KHZ对应每次获取得到的字节数据为1920,而这个两个数字由于我我自己还没搞懂为何是这两个大小,有待以后解决,但不影响后面编码的操作。
(2)由于我们编码是按照ffmpeg的规则进行编码的,所以有必要知道每一次需要采集多大的PCM数据给编码函数,由于ffmpeg的frame_size默认的是1024,即每次的采样时需要采集1024个样本[1],同时因为PCM的如果是两声道的16位AV_SAMPLE_FMT_S16格式的音频数据,一次需要送给swr_convert()重采样函数的数据大小为1024*2*2字节,即4096字节,swr_convert的函数定义[2]如下:
int swr_convert(struct SwrContext *s, uint8_t **out, int out_count,const uint8_t **in , int in_count);
其中:
(a)In_count ,Out_count 都是指number of audio samples (per channel)即每个声道的采样个数,对于AAC而言每个声道采样个数为1024,对于MP3而言每个声道采样 个数为1152。
(b)同时对于这两个数值的大小该如何确定,可以分为如下两种情况:
==>只要采样率降小或不变的情况下(48000->44100或44100->44100),我们直接传dst_nb_samples=src_nb_samples是肯定没问题的,对于本文而言因为是 AV_SAMPLE_FMT_S16->AV_SAMPLE_FMT_FLTP采样频率不变,因此设置为1024即可
==>如果是44100->48000,你就传dst_nb_samples=2*src_nb_samples,就可以了,不需要你计算。具体对于这个函数的深入理解可参考链接[2],该博主讲的更为通 透。
(3)由于上述注意要点(1)和(2)所示每次从声卡获取到的数据大小为1920字节(48kHz)或者1792字节(44.1KHz)并不是与4096字节的存在倍数关系,因此若要考虑不要将数据存在丢失而引起噪音的情况,就需要考虑其他们之间的最小公倍数的关系:
4096和1792的最小公倍数为 : 28672=4096*7=1792*16
4096和1920的最小公倍数为 : 61440=4096*15=1920*32
从上述关系可以看出为了不丢失音频数据且编程遍历的方案可以采取如下:
(a)当采样频率为44.1Khz时,每14次从声卡采集PCM数据后,然后一次性进行32次音频重采样和编码。
(b)当采样频率为48Khz时,每15次从声卡采集PCM数据后,然后一次性进行32次音频重采样和编码。
二、CoreAudio音频采集及demo讲解
(1)如下为使用CoreAudio采集声卡音频的初始化工作函数StartCapture()对于上述参数的初始化的关键代码:
…..
hr = m_pAudioClient->GetMixFormat(&m_pwfx);
if (FAILED(hr))
{
xhlog("m_pAudioClient->GetMixFormat erro\n");
printf("m_pAudioClient->GetMixFormat erro\n");
goto error;
}
…..
if (m_pwfx->nSamplesPerSec == 44100)//根据音频采样频率的不同,而进行参数的设置,包括所用到缓存数据区的大小
{
type_frequency_audio = TYPE_RATE_44100HZ;
samplerate = 44100;
size_pcm_data = 4096 * 14;
size_pcm_onepic = 1792;
}
else if (m_pwfx->nSamplesPerSec == 48000)
{
type_frequency_audio = TYPE_RATE_48000HZ;
samplerate = 48000;
size_pcm_data = 4096 * 15;
size_pcm_onepic = 1920;
}
上述代码针对从声卡中获取得到的采样频率不同而设置不同的数据缓存区大小,对于如何使用CoreAudio的API可参考链接[3],里面有详细讲解,也可下载本文的demo来学习参考。
(2)对于采集和重采样及编码的关键代码如下:
(a)在本例程中的My_DoWork()函数中,当采样32次后一整块数据放进链表中然后等待重采样和编码,此处需要注意到一点就是当采集第33次数据时,需要将第33次的数据 保存起来留作下一个循环的第一次数据,否则会出现噪音,其中count_pcm_save从0开始计算:
else if (count_pcm_save == 32)
{
//开启线程
list_node_audio_data list_audio_node_tmp = (list_node_audio_data)malloc(sizeof(struct stru_audio_data));
memcpy(list_audio_node_tmp->audio_currenTime, audio_currenTime, sizeof(audio_currenTime));
memcpy(list_audio_node_tmp->data_pcm_tmp, data_pcm_tmp, size_pcm_data);
//插入数据到链表中
list_insert_pcm_data(list_pcm_data_head, list_audio_node_tmp);
count_pcm_save = 0;//清零
free(data_pcm_tmp);//释放内存资源
//此处接收到了新的一个循环的一帧,需要保存处理,否则出现杂声,更加采集频率确定
memset(Data_Last_Frame, 0, sizeof(Data_Last_Frame));
memcpy(Data_Last_Frame, pData, size_pcm_onepic);
break;
}
(b)new_ffmpeg_encode_aac_api2()函数为重采样及编码的函数,其中count_loop为14或者15(根据采样频率而定),注意outs_new为char*outs_new[2]格式,因为重采样 后需要使用outs_new[0]和outs_new[1]分别存放左声道和右声道的数据:
void new_ffmpeg_encode_aac_api2(PVOID lpParam)
{
SDL_LockMutex(encode_aac_data2);//加麦克风和扬声器的锁
int count_encode = 0;
int ret = 0;
AVPacket pkt = { 0 };
av_init_packet(&pkt);
int count_loop = 0;//当44.1khz时遍历14次,当为48khz时遍历15次
if (samplerate == 44100)
{
count_loop = 14;
}
else if (samplerate == 48000)
{
count_loop = 15;
}
avformat_write_header(ic, NULL);//写aac的文件头
char *buf = (char *)( ( (list_node_audio_data)lpParam)->data_pcm_tmp);//获取采集到的pcm数据为s16格式
//for (count_encode = 0; count_encode < 7; )
for (count_encode = 0; count_encode < count_loop; )
{
//进程重采样数据
const uint8_t *indata[AV_NUM_DATA_POINTERS] = { 0 };
indata[0] = (uint8_t *)(buf + 4096 * count_encode);
int len = swr_convert(asc, outs_new, pcm->nb_samples, indata, pcm->nb_samples);
pcm->data[0] = outs_new[0];
pcm->data[1] = outs_new[1];
pcm->pkt_dts = pcm->pts = ((long long*)(((list_node_audio_data)lpParam)->audio_currenTime))[count_encode];
//进行编码aac
ret = avcodec_send_frame(ac, pcm);//将pcm送入编码器中
ret = avcodec_receive_packet(ac, &pkt);//此处的编码出来的是裸流,数据没有AAC常规的头7个字节
if (pkt.pts < 0)
{
printf("pkt.pts < 0\n");
av_packet_unref(&pkt);
count_encode++;
continue;
}
ret = av_write_frame(ic, &pkt);
av_packet_unref(&pkt);
count_encode++;
}
av_write_trailer(ic);//写文件尾
free((list_node_audio_data)lpParam);
SDL_UnlockMutex(encode_aac_data2);//加麦克风和扬声器的锁
return ;
}
三、总结
到此已经将整个PCM编码重采样的流程讲解了一遍,重点是讲述如何处理44.1KHZ和48KHZ采样频率不同时,如何处理数据的细节思路,对于如何进行重采样、编码以及使用CoreAudio的 API可以参考我列出来的参考链接,都可以很详细的学习到,如果对本文有技术上的问题也可以联系我,一同交流学习。
四、本例程的demo免积分下载地址
https://download.csdn.net/download/weixin_41353840/13120963
参考链接:
[1]https://blog.csdn.net/XIAIBIANCHENG/article/details/72810495?utm_source=app
[3]https://blog.csdn.net/zdy_ruoshui/article/details/17138665
每写一篇文章都不容易,尊重别人的知识产权才是对自己和技术的尊重。为了避免发生知识产权被侵权的情况,我决定做出以下声明:
1.博客中标注原创的文章,版权归原作者 吴豪乐工作室 所有;
2.未经原作者允许不得转载本文内容,否则将视为侵权;
3.转载或者引用本文内容请注明来源及原作者;
4.对于不遵守此声明或者其他违法使用本文内容者,本人依法保留追究权等。