数据先同时通过 时域转频域变换器和心理学模型处理数据,前者将数据转换成多种频段的数据,然后剔除不需要的频段数据,后者会去除非人耳听到的范围声音和一些复合声音,最后将两者合并经过量化编码,无损编码之类的,形成比特流数据,在此之前还会有一些辅助数据,此后数据就会变得非常小;
opus、aac、Ogg、Speex、iLBC、AMR、G.711, 最常用的编码器是opus aac。
opus常用于直播,尤其是无延迟的直播,webrtc默认使用opus;
AAC是应用最广泛的编解码;
Ogg收费;
Speex支持回音消除;
G.711一般用于固定电话,声音损耗严重,通话会失真;
本文福利, 免费领取C++音视频学习资料包+学习路线大纲、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs),有需要的可以进企鹅裙927239107领取哦~
AAC比较适合有一定延迟的直播,AAC-LD属于低延迟编码器
常用的规格有AAC LC、AAC HE V1 、AAC HE V2三种;
AAC HE V1 = AAC + SBR;
AAV HE V2 = AAC + SBR + PS;
目前AAC HE V1 已经被取代 V2 取代了;
V2的码流跟V1的差别不是很大,根据声音的数据变化,如果两个声道的差别很大,码流差别就会越小;
就相当于在aac数据前面加了个Header,header里面就会包含aac数据的一些信息,方便进行编解码
Audio Object Type: 在代码中实际获取类型的时候需要进行+1,才是下面的类型
1 == AAC main
2 == AAC LC
5 == SBR == HE V1
29 == ps == HE V2
其中的采样率是通过十进制数表示的一个采样率,有一个表,比如:0 == 96000Hz 1 == 88200HZ 等
获取本地签名证书列表:/usr/bin/security find-identity -v -p codesigning
查看动态库是否签名: codesign -d -vv 动态库文件
签名命令:codesign -fs "iPhone Distribution: 你的签名证书." 动态库文件
xcode环境:13.2.1
签名了如果还是报错,关掉沙盒并且设置 Enable Hardened Runtime 为NO
在项目中设置user header search path的时候,要使用全路径方式,我使用$(PROJECT_NAME)方式,有的头文件在链接的时候会报错;
void startRecorder(void) {
// 上下文
AVFormatContext *av_context = NULL;
AVDictionary *options = NULL;
// 1. 注册设备
avdevice_register_all();
// 2. 设置采集方式
//设置采集方式 mac os 下是AVfoundation Windows下是dshow linux 下是alsa
AVInputFormat *format = av_find_input_format("avfoundation");
// 3. 打开设备
//里面的识别格式为[[video device]:[audio device]] 这里写0 是获取第1个音频设备
char *name = ":0";
// url 是路径 可以是网络路径也可以是本地路径 本地路径mac下的格式是 video : audio 这里表示获取第一个音频设备
int result = avformat_open_input(&av_context, name, format, &options);
if (result != 0) {
char errors[1024];
// 根据返回值生成错误信息
av_make_error_string(errors, 1024, result);
printf("打开设备失败:%s\n", errors);
return;
}
printf("打开设备成功!\n");
get_audio_packet(av_context,&packet_callback);
// 关闭输入 上下文
avformat_close_input(&av_context);
}
void get_audio_packet(AVFormatContext *context, void (*packet_callback)(AVPacket)) {
// w == 写 b == 二进制 + == 没有就创建文件
FILE *f = fopen("/Users/cunw/Desktop/learning/音视频学习/音视频文件/code_recorder.pcm", "wb+");
AVPacket *packet = av_packet_alloc();
int result = -1;
// 循环读取设备信息
// result == -35 是Resource temporarily unavailable 因为获取太频繁 设备未准备好,还正在处理数据
// 因为输入设备准备好需要时间 睡一秒后再读取
sleep(1.0);
while ((result = av_read_frame(·context, packet)) == 0 || result == -35) {
if (packet->size > 0) {
packet_callback(*packet);
fwrite(packet->data, packet->size, 1, f);
// 每读取一次 就清空数据包 不然数据包会一直增大
av_packet_unref(packet);
}
}
if (result != 0) {
char errors[1024];
av_make_error_string(errors, 1024, result);
printf("get packet occured error is \"%s\" \n", errors);
}
// 将缓冲区剩余的数据 强制写入文件
fflush(f);
fclose(f);
// 释放packet空间
av_packet_free(&packet);
}
// 回调函数
void packet_callback(AVPacket packet) {
printf("packet size is %d\n",packet.size);
}
就是将音频三元组(采样率 采样大小 通道数)的值转成另外一组值
1、从设备采集的音频数据与编码器要求的不一致;
2、扬声器要求的音频数据与要播放的音频数据不一致;
3、方便运算:例如回音消除 将多声道变为单声道;
api:需要使用libswresample库
- swr_alloc_set_opts 通过设置采样参数获取上下文
- 参数大体分为输出的采样率、采样大小、声道和输入的采样率、采样大小、声道;
- out_ch_layout:表示声道也可以是布局(扬声器的布局)AV_CH_LAYOUT_STEREO 立体声;
- out_sample_fmt:输出的采样格式 16 = AV_SAMPLE_FMT_S16 或者 32 = AV_SAMPLE_FMT_FLT;
- av_sample_fmt_s16 in_ch_layout:输入的声道布局 ;
- in_sample_fmt 输入的采样格式 ;
- in_sample_rate: 输入的采样率;
- 后两位是log相关 0,null
- swr_init 初始化上下文
- swr_convert 开始转换 ,目的就是将输入缓冲区的数据写入输出缓冲区
out:输出结果缓冲区 out_count:每个通道的采样数
in:输入的缓冲区 in_count:输入的单个通道的采样数
- 因为重采样的数据需要重新构造所以需要创建输入缓冲区和输出缓冲区
使用av_sample_array_and_samples audio_data创建
其中的单通道采样数(单位是字节)`nb_samples = pkt.size / (32位 / 8) / 2(通道数)`
linessize:缓冲区大小 align:对齐 0
- 在转换前需要将pkt的data按字节拷贝到输入缓冲区,调用memcpy需要引用string.h
- 将输出数据写入文件
将输出缓冲区已经转换的数据写入文件
- 还有输入输出缓冲区的释放av_freep
- swr_free释放上下文
重采样上下文初始化代码
SwrContext * init_swr_context(void) {
SwrContext *context = NULL;
// 假设已经提前知道输入音频数据的三要素的值 AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_FLT, 44100
context = swr_alloc_set_opts(NULL,
AV_CH_LAYOUT_STEREO,
AV_SAMPLE_FMT_S16,
44100,
AV_CH_LAYOUT_STEREO,
AV_SAMPLE_FMT_FLT,
44100,
0, NULL);
int result = swr_init(context);
if (result != 0) {
char error[1024];
av_make_error_string(error, 1024, result);
printf("初始化重采样上下文失败:%s", error);
}
return context;
}
将采集的数据重采样后 写入文件代码
void get_audio_packet(AVFormatContext *context, void (*packet_callback)(AVPacket)) {
// w == 写 b == 二进制 + == 没有就创建文件
FILE *f = fopen("/Users/cunw/Desktop/learning/音视频学习/音视频文件/resample.pcm", "wb+");
// 初始化重采样上下文
SwrContext *swr_context = init_swr_context();
// 初始化转换的输入输出缓冲区
uint8_t **out_buffer = NULL;
int linesize_out = 0;
av_samples_alloc_array_and_samples(&out_buffer, &linesize_out, 2, 512, AV_SAMPLE_FMT_S16, 0);
uint8_t **in_buffer = NULL;
int linesize_in = 0;
// nb_samples 单通道采样数 4096 / (32 / 8) / 2 = 1024
av_samples_alloc_array_and_samples(&in_buffer, &linesize_in, 2, 512, AV_SAMPLE_FMT_FLT, 0);
AVPacket *packet = av_packet_alloc();
int result = -1;
// 循环读取设备信息
// result == -35 是Resource temporarily unavailable 因为获取太频繁 设备未准备好,还正在处理数据
sleep(1);
while (((result = av_read_frame(context, packet)) == 0 || result == -35) && isRecording == 1) {
if (packet->size > 0) {
// 开始转换数据
// 先将音频数据拷贝到输入缓冲区 只是重采样音频的话 只需要处理数组的第一个
memcpy(in_buffer[0], packet->data, packet->size);
// 再进行转换
swr_convert(swr_context, out_buffer, 512, (const uint8_t **)in_buffer, 512);
fwrite(out_buffer[0],linesize_out, 1, f);
// 每读取一次 就清空数据包 不然数据包会一直增大
av_packet_unref(packet);
}
}
if (result != 0) {
char errors[1024];
av_make_error_string(errors, 1024, result);
printf("get packet occured error is \"%s\" \n", errors);
}
// 释放重采样资源
if (in_buffer) {
av_freep(&in_buffer[0]);
}
if (out_buffer) {
av_freep(&out_buffer[0]);
}
av_freep(&in_buffer);
av_freep(&out_buffer);
swr_free(&swr_context);
// 将缓冲区剩余的数据 强制写入文件
fflush(f);
fclose(f);
// 释放packet空间
av_packet_free(&packet);
}
本文福利, 免费领取C++音视频学习资料包+学习路线大纲、技术视频/代码,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,编解码,推拉流,srs),有需要的可以进企鹅裙927239107领取哦~
在使用fdk_aac编码器的时候,由于默认的ffmpeg有自带的aac,所以通过avcodec_find_encoder_by_name("libfdk_aac")就获取不到。在编译的时候加上--enable-libfdk-aac。注意:重新编译安装ffmpeg之前最好先删掉之前的ffmpeg,然后更新项目中的动态库;
如果还不行,试试单独下载安装[fdk_aac](https://www.linuxfromscratch.org/blfs/view/svn/multimedia/fdk-aac.html),再重新编译ffmpeg
在结束的时候释放frame(av_frame_free) 和packet(av_packet_frame);
AVCodecContext* init_codec_context(void) {
// 创建aac编码器
AVCodec *codec = avcodec_find_encoder_by_name("libfdk_aac");
// 初始化上下文
AVCodecContext *context = NULL;
context = avcodec_alloc_context3(codec);
context->sample_fmt = AV_SAMPLE_FMT_S16;
context->sample_rate = 44100;
context->channel_layout = AV_CH_LAYOUT_STEREO;
context->bit_rate = 0;
// bitrate == 0 才会生效
context->profile = FF_PROFILE_AAC_HE_V2;
int result = avcodec_open2(context, codec, NULL);
if (result < 0) {
char error[1024];
av_make_error_string(error, 1024, result);
av_log(NULL, AV_LOG_DEBUG, "创建AAC编码器失败:%s",error);
}
return context;
}
AVFrame* create_audio_input_frame(void) {
AVFrame *codec_frame = NULL;
codec_frame = av_frame_alloc();
codec_frame->nb_samples = 512;
codec_frame->channel_layout = AV_CH_LAYOUT_STEREO;
codec_frame->format = AV_SAMPLE_FMT_S16;
int buffer_result = av_frame_get_buffer(codec_frame, 0);
if (buffer_result < 0) {
char error[1024];
av_make_error_string(error, 1024, buffer_result);
printf("frame 缓冲区分配失败:%s", error);
}
return codec_frame;
}
void audio_encode(AVCodecContext *ctx, AVFrame *frame, AVPacket *packet, FILE *fl) {
// 将数据送入编码器
int codec_result = avcodec_send_frame(ctx, frame);
while (codec_result >= 0) {
// 从packet中循环读取编码好的数据
codec_result = avcodec_receive_packet(ctx, packet);
if (codec_result == AVERROR(EAGAIN) || codec_result == AVERROR_EOF) {
break;
} else if (codec_result < 0) {
char error[1024];
av_make_error_string(error, 1024, codec_result);
printf("编码器出错:%s 停止编码", error);
} else {
fwrite(packet->data, 1,packet->size, fl);
}
}
if (codec_result < 0) {
char error[1024];
av_make_error_string(error, 1024, codec_result);
printf("将数据送入编码器错误: %s\n",error);
}
}
memcpy(codec_frame->data[0], out_buffer[0], linesize_out);
audio_encode(codec_context, codec_frame, codec_packet, f);
void get_audio_packet(AVFormatContext *context, void (*packet_callback)(AVPacket)) {
// w == 写 b == 二进制 + == 没有就创建文件
FILE *f = fopen("/Users/cunw/Desktop/learning/音视频学习/音视频文件/encoder.aac", "wb+");
// 创建编码器上下文
AVCodecContext *codec_context = init_codec_context();
// 初始化输入缓冲区 AVframe
AVFrame *codec_frame = create_audio_input_frame();
// 初始化编码输出缓冲区
AVPacket *codec_packet = av_packet_alloc();
// 初始化重采样上下文
SwrContext *swr_context = init_swr_context();
// 初始化重采样的缓冲区
uint8_t **out_buffer = NULL;
int linesize_out = 0;
uint8_t **in_buffer = NULL;
int linesize_in = 0;
init_resammple_buffer(&in_buffer, &linesize_in, &out_buffer, &linesize_out);
AVPacket *packet = av_packet_alloc();
int result = -1;
// 循环读取设备信息
while (isRecording == 1) {
result = av_read_frame(context, packet);
if (packet->size > 0 && result == 0) {
packet_callback(*packet);
// 开始转换数据
// 先将音频数据拷贝到输入缓冲区 只是重采样音频的话 只需要处理数组的第一个
memcpy(in_buffer[0], packet->data, packet->size);
// 再进行转换
swr_convert(swr_context, out_buffer, 512, (const uint8_t **)in_buffer, 512);
// 将重采样好的数据按字节拷贝到frame缓冲区
memcpy(codec_frame->data[0], out_buffer[0], linesize_out);
audio_encode(codec_context, codec_frame, codec_packet, f);
// 每读取一次 就清空数据包 不然数据包会一直增大
av_packet_unref(packet);
} else if (result == -EAGAIN) {
// result == -35 是Resource temporarily unavailable 因为设备未准备好,还正在处理数据
av_usleep(1);
}
}
// 把缓冲区剩余的数据拿出来编码
audio_encode(codec_context, NULL, codec_packet, f);
if (result != 0) {
char errors[1024];
av_make_error_string(errors, 1024, result);
printf("get packet occured error is \"%s\" \n", errors);
}
// 释放重采样资源
if (in_buffer) {
av_freep(&in_buffer[0]);
}
if (out_buffer) {
av_freep(&out_buffer[0]);
}
av_freep(&in_buffer);
av_freep(&out_buffer);
swr_free(&swr_context);
av_frame_free(&codec_frame);
av_packet_free(&codec_packet);
// 将缓冲区剩余的数据 强制写入文件
fflush(f);
fclose(f);
// 释放packet空间
av_packet_free(&packet);
}