使用FFmpeg把PCM裸数据编码成AAC音频流,具体步骤跟YUV编码成H264差不多。
1、命令行
ffmpeg -f s16le -ar 44100 -ac 2 -i bb1.pcm output.aac
-f
PCM数据为s16le
-ar
采样率为44100
-ac
通道数为2
这样就通过命令把PCM数据编码成AAC了。
2、使用API编码
FFmpeg内部AAC格式只支持AV_SAMPLE_FMT_FLTP格式的PCM,由于我们的PCM数据是s16le的,因此我们需要把s16le格式转换成fltp格式再进行编码。我们可以在AVCodec结构体中的sample_fmts字段中判断编码器是否支持你的格式。
-
初始化输出文件上下文
int avformat_alloc_output_context2(AVFormatContext **ctx, ff_const59 AVOutputFormat *oformat, const char *format_name, const char *filename);
ctx
输出文件的上下文oformat
输出文件的AVOutputFormat,传NULL,FFmpeg会根据filename的格式初始化oformatformat_name
输出文件的格式, 传NULL,FFmpeg会根据filename的格式初始化format_namefilename
输出文件路径 -
初始化编码器上下文
dec = avcodec_find_encoder(ofmt_ctx->oformat->audio_codec); if (!dec) { printf("avcodec_find_encoder fail \n"); goto __FAIL; } dec_ctx = avcodec_alloc_context3(dec); dec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP; if (!check_sample_fmt(dec, dec_ctx->sample_fmt)) { fprintf(stderr, "Encoder does not support sample format %s", av_get_sample_fmt_name(dec_ctx->sample_fmt)); goto __FAIL; } dec_ctx->channel_layout = select_channel_layout(dec); dec_ctx->channels = av_get_channel_layout_nb_channels(dec_ctx->channel_layout); dec_ctx->sample_rate = select_sample_rate(dec); dec_ctx->bit_rate = 64000; ret = avcodec_open2(dec_ctx, dec, NULL);
FFmpeg内部AAC音频流只支持fltp格式的PCM,使用check_sample_fmt函数可以检测编码器是否支持AV_SAMPLE_FMT_FLTP,通过select_channel_layout函数选择最佳的音频通道布局,通过select_sample_rate函数选择最佳的采样率。
检测是否支持AVSampleFormat
static int check_sample_fmt(const AVCodec *codec, enum AVSampleFormat sample_fmt) { const enum AVSampleFormat *p = codec->sample_fmts; while (*p != AV_SAMPLE_FMT_NONE) { if (*p == sample_fmt) return 1; p++; } return 0; }
选择最佳采样率
static int select_sample_rate(const AVCodec *codec) { const int *p; int best_samplerate = 0; if (!codec->supported_samplerates) return 44100; p = codec->supported_samplerates; while (*p) { if (!best_samplerate || abs(44100 - *p) < abs(44100 - best_samplerate)) best_samplerate = *p; p++; } return best_samplerate; }
选择最佳通道布局
static int select_channel_layout(const AVCodec *codec) { const uint64_t *p; uint64_t best_ch_layout = 0; int best_nb_channels = 0; if (!codec->channel_layouts) return AV_CH_LAYOUT_STEREO; p = codec->channel_layouts; while (*p) { int nb_channels = av_get_channel_layout_nb_channels(*p); if (nb_channels > best_nb_channels) { best_ch_layout = *p; best_nb_channels = nb_channels; } p++; } return best_ch_layout; }
-
创建输入文件音频流
AVStream *st = avformat_new_stream(ofmt_ctx, dec); ret = avcodec_parameters_from_context(st->codecpar, dec_ctx); if (ret<0) { printf("avcodec_parameters_from_context fail \n"); goto __FAIL; }
把编码器上下文参数拷贝给新建的AVSteam
-
打开输出文件
avio_open(&ofmt_ctx->pb, aacPath.UTF8String, AVIO_FLAG_WRITE);
-
写入文件头
avformat_write_header(ofmt_ctx, NULL);
-
读取PCM数据,放到AVFrame中
-
初始化AVFrame用来存放PCM数据
AVFrame *s16_frame = av_frame_alloc(); if (!s16_frame) { printf("av_frame_alloc fail \n"); goto __FAIL; } s16_frame->nb_samples = dec_ctx->frame_size; s16_frame->format = AV_SAMPLE_FMT_S16; s16_frame->channel_layout = AV_CH_LAYOUT_STEREO; s16_frame->sample_rate = 44100; ret = av_frame_get_buffer(s16_frame, 0);
AVFrame的参数要与你的PCM数据参数一致,我用到的PCM数据是s16le、采样率44100Hz、通道数为2。
-
从文件中读取PCM数据
size_t size = fread(pcm_buffer, 1, pcm_buffer_size, pcm_f);
-
存放到AVFrame中去
av_samples_fill_arrays(s16_frame->data, s16_frame->linesize, pcm_buffer, s16_frame->channels, s16_frame->nb_samples, s16_frame->format, 0); int av_samples_fill_arrays(uint8_t **audio_data, int *linesize, const uint8_t *buf, int nb_channels, int nb_samples, enum AVSampleFormat sample_fmt, int align);
audio_data
输出buffer,传frame->data即可linesize
输出buffer的行大小,传frame->linesize即可buf
音频数据nb_channels
音频通道数nb_samples
音频采样数sample_fmt
音频数据格式align
buffer的对齐方式 默认为0,不对齐传1
-
【学习地址】:FFmpeg/WebRTC/RTMP/NDK/Android音视频流媒体高级开发
【文章福利】:免费领取更多音视频学习资料包、大厂面试题、技术视频和学习路线图,资料包括(C/C++,Linux,FFmpeg webRTC rtmp hls rtsp ffplay srs 等等)有需要的可以点击1079654574加群领取哦~
-
s16le->fltp格式转换
-
创建SwrContext
struct SwrContext *swr_alloc_set_opts(struct SwrContext *s, int64_t out_ch_layout, enum AVSampleFormat out_sample_fmt, int out_sample_rate, int64_t in_ch_layout, enum AVSampleFormat in_sample_fmt, int in_sample_rate, int log_offset, void *log_ctx);
s
传NULL即可,会自动分配空间创建SwrContextin_ch_layout
out_ch_layout
输入、输出的通道布局in_sample_fmt
out_sample_fmt
输入、输出的PCM数据格式in_sample_rate
out_sample_rate
输入、输出的采样率 -
初始化SwrContext
int swr_init(struct SwrContext *s);
-
格式转换
int swr_convert(struct SwrContext *s, uint8_t **out, int out_count, const uint8_t **in , int in_count);
in
out
输入、输出的bufferin_count
out_count
输入、输出的采样数,需要注意的是,这里传的是一个通道的采样数,而不是多个通道数相加的。
-
-
编码
int avcodec_send_frame(AVCodecContext *avctx, const AVFrame *frame); int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt);
-
写文件尾
int av_write_trailer(AVFormatContext *s);
完整代码如下文章来源:https://www.toymoban.com/news/detail-680482.html
/* check that a given sample format is supported by the encoder */
static int check_sample_fmt(const AVCodec *codec, enum AVSampleFormat sample_fmt)
{
const enum AVSampleFormat *p = codec->sample_fmts;
while (*p != AV_SAMPLE_FMT_NONE) {
if (*p == sample_fmt)
return 1;
p++;
}
return 0;
}
/* just pick the highest supported samplerate */
static int select_sample_rate(const AVCodec *codec)
{
const int *p;
int best_samplerate = 0;
if (!codec->supported_samplerates)
return 44100;
p = codec->supported_samplerates;
while (*p) {
if (!best_samplerate || abs(44100 - *p) < abs(44100 - best_samplerate))
best_samplerate = *p;
p++;
}
return best_samplerate;
}
/* select layout with the highest channel count */
static int select_channel_layout(const AVCodec *codec)
{
const uint64_t *p;
uint64_t best_ch_layout = 0;
int best_nb_channels = 0;
if (!codec->channel_layouts)
return AV_CH_LAYOUT_STEREO;
p = codec->channel_layouts;
while (*p) {
int nb_channels = av_get_channel_layout_nb_channels(*p);
if (nb_channels > best_nb_channels) {
best_ch_layout = *p;
best_nb_channels = nb_channels;
}
p++;
}
return best_ch_layout;
}
+ (void)convert
{
NSString *pcmPath = [[NSBundle mainBundle] pathForResource:@"bb1_44100_2_s16le.pcm" ofType:nil];
NSString *aacPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"bb1.aac"];
NSLog(@"%@", aacPath);
int ret;
AVFormatContext *ofmt_ctx = NULL;
AVCodecContext *dec_ctx = NULL;
AVCodec *dec = NULL;
AVPacket *pkt = NULL;
AVFrame *s16_frame = NULL;
AVFrame *fltp_frame = NULL;
SwrContext *swr_ctx = NULL;
FILE *pcm_f = fopen(pcmPath.UTF8String, "rb+");
ret = avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, aacPath.UTF8String);
if (ret<0) {
printf("avformat_alloc_output_context2 fail \n");
goto __FAIL;
}
dec = avcodec_find_encoder(ofmt_ctx->oformat->audio_codec);
if (!dec) {
printf("avcodec_find_encoder fail \n");
goto __FAIL;
}
dec_ctx = avcodec_alloc_context3(dec);
dec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
if (!check_sample_fmt(dec, dec_ctx->sample_fmt)) {
fprintf(stderr, "Encoder does not support sample format %s",
av_get_sample_fmt_name(dec_ctx->sample_fmt));
goto __FAIL;
}
dec_ctx->channel_layout = select_channel_layout(dec);
dec_ctx->channels = av_get_channel_layout_nb_channels(dec_ctx->channel_layout);
dec_ctx->sample_rate = select_sample_rate(dec);
dec_ctx->bit_rate = 64000;
ret = avio_open(&ofmt_ctx->pb, aacPath.UTF8String, AVIO_FLAG_WRITE);
if (ret<0) {
printf("avio_open fail \n");
goto __FAIL;
}
ret = avcodec_open2(dec_ctx, dec, NULL);
if (ret<0) {
printf("avcodec_open2 fail \n");
goto __FAIL;
}
AVStream *st = avformat_new_stream(ofmt_ctx, dec);
ret = avcodec_parameters_from_context(st->codecpar, dec_ctx);
if (ret<0) {
printf("avcodec_parameters_from_context fail \n");
goto __FAIL;
}
ret = avformat_write_header(ofmt_ctx, NULL);
if (ret<0) {
printf("avformat_write_header fail \n");
goto __FAIL;
}
s16_frame = av_frame_alloc();
if (!s16_frame) {
printf("av_frame_alloc fail \n");
goto __FAIL;
}
s16_frame->nb_samples = dec_ctx->frame_size;
s16_frame->format = AV_SAMPLE_FMT_S16;
s16_frame->channel_layout = AV_CH_LAYOUT_STEREO;
s16_frame->sample_rate = 44100;
// s16_frame->channels = av_get_channel_layout_nb_channels(s16_frame->channel_layout);
ret = av_frame_get_buffer(s16_frame, 0);
if (ret<0) {
printf("av_frame_get_buffer fail \n");
goto __FAIL;
}
pkt = av_packet_alloc();
if (!pkt) {
printf("av_packet_alloc fail \n");
goto __FAIL;
}
int pts_i = 0;
swr_ctx = swr_alloc_set_opts(NULL, dec_ctx->channel_layout, dec_ctx->sample_fmt, dec_ctx->sample_rate, s16_frame->channel_layout, s16_frame->format, s16_frame->sample_rate, 0, NULL);
if (!swr_ctx) {
printf("swr_alloc_set_opts fail \n");
goto __FAIL;
}
ret = swr_init(swr_ctx);
if (ret<0) {
printf("swr_init fail \n");
goto __FAIL;
}
fltp_frame = av_frame_alloc();
fltp_frame->nb_samples = dec_ctx->frame_size;
fltp_frame->format = dec_ctx->sample_fmt;
fltp_frame->channel_layout = dec_ctx->channel_layout;
fltp_frame->sample_rate = dec_ctx->sample_rate;
// fltp_frame->channels = av_get_channel_layout_nb_channels(s16_frame->channel_layout);
ret = av_frame_get_buffer(fltp_frame, 0);
if (ret<0) {
printf("av_frame_get_buffer fail \n");
goto __FAIL;
}
uint64_t pcm_buffer_size = s16_frame->nb_samples*av_get_bytes_per_sample(s16_frame->format)*s16_frame->channels;
uint8_t *pcm_buffer = av_malloc(pcm_buffer_size);
while (feof(pcm_f)==0) {
size_t size = fread(pcm_buffer, 1, pcm_buffer_size, pcm_f);
int nb_samples = size/(av_get_bytes_per_sample(s16_frame->format)*s16_frame->channels);
s16_frame->nb_samples = nb_samples;
fltp_frame->nb_samples = nb_samples;
av_samples_fill_arrays(s16_frame->data, s16_frame->linesize, pcm_buffer, s16_frame->channels, s16_frame->nb_samples, s16_frame->format, 0);
ret = swr_convert(swr_ctx, fltp_frame->data, fltp_frame->nb_samples, s16_frame->data, s16_frame->nb_samples);
if (size==0) {
printf("fread fail \n");
break;
}
pts_i+=fltp_frame->nb_samples;
fltp_frame->pts = pts_i;
ret = avcodec_send_frame(dec_ctx, fltp_frame);
if (ret<0) {
printf("avcodec_send_frame fail \n");
break;
}
while (1) {
ret = avcodec_receive_packet(dec_ctx, pkt);
if (ret==AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
} else if (ret<0) {
printf("avcodec_receive_packet fail \n");
break;
}
ret = av_interleaved_write_frame(ofmt_ctx, pkt);
if (ret<0) {
printf("av_interleaved_write_frame fail \n");
break;
}
av_packet_unref(pkt);
}
}
ret = avcodec_send_frame(dec_ctx, NULL);
if (ret<0) {
printf("avcodec_send_frame fail \n");
goto __FAIL;
}
while (1) {
ret = avcodec_receive_packet(dec_ctx, pkt);
if (ret==AVERROR(EINVAL) || ret == AVERROR_EOF) {
break;
} else if (ret<0) {
printf("avcodec_receive_packet fail \n");
break;
}
ret = av_interleaved_write_frame(ofmt_ctx, pkt);
if (ret<0) {
printf("av_interleaved_write_frame fail \n");
break;
}
av_packet_unref(pkt);
}
ret = av_write_trailer(ofmt_ctx);
if (ret<0) {
printf("av_write_trailer fail \n");
}
__FAIL:
if (ofmt_ctx->pb) {
avio_close(ofmt_ctx->pb);
}
if (dec_ctx) {
avcodec_close(dec_ctx);
}
if (pcm_buffer) {
av_free(pcm_buffer);
}
if (ofmt_ctx) {
avformat_free_context(ofmt_ctx);
}
if (s16_frame) {
av_frame_free(&s16_frame);
}
if (fltp_frame) {
av_frame_free(&fltp_frame);
}
if (pkt) {
av_packet_free(&pkt);
}
}
原文链接:【FFmpeg】PCM编码成AAC - 简书文章来源地址https://www.toymoban.com/news/detail-680482.html
到了这里,关于【FFmpeg】PCM编码成AAC的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!