编码流程
-
查找编码器
-
创建编码器上下文
-
设置编码器参数
编码的一些信息,
-
编码器与编码器上下文绑定在一起
-
创建输出编码后的文件
-
创建(获取)AVFrame
这个是原始的帧数据。
-
创建AVPacket
编码后的是音频帧是保存在数据包中的。一个数据包包括多个数据帧。
-
生成(获取)音频内容。
-
进行编码。
对音频内容进行编码。
-
得到编码后数据写入到输出文件
流程图如下:
实现代码
自定义生成音频数据,生成编码格式为aac的编码文件
#include <libavutil/log.h>
#include <libavutil/opt.h>
#include <libavutil/samplefmt.h>
#include <libavcodec/avcodec.h>
#include <stdio.h>
#include <libavutil/avutil.h>
#include <libavformat/avformat.h>
#include <SDL.h>
#include <stdbool.h>
// 从编码器中获取支持的最佳的simple_rate
static int select_best_sample_rate(const AVCodec* codec) {
const int* p;
int best_samplerate = 0;
// codec->supported_samplerates 属性是指向表示编解码器支持哪些采样率的整数指针数组的指针
if (!codec->supported_samplerates) {
// 如果该编码器不支持采样率
return 44100;
}
p = codec->supported_samplerates;
// 以44100为标杆,谁离它近 选谁。
while (*p) {
if (!best_samplerate || abs(44100 - *p) < abs(44100 - best_samplerate)) {
best_samplerate = *p;
}
p++;
}
return best_samplerate;
}
// 检查次编码器codec 是否支持sample_fmt这种采样格式
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 encode(AVCodecContext* ctx, AVFrame* frame, AVPacket* pkt, FILE* out) {
int ret = -1;
// 把数据帧送入编码器
ret = avcodec_send_frame(ctx, frame);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Failed to send frame to encoder: %s!\n", av_err2str(ret));
goto _END;
}
while (ret >= 0) {
ret = avcodec_receive_packet(ctx, pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
return 0;
}
else if (ret < 0) {
return -1; //退出tkyc
}
av_log(NULL, AV_LOG_DEBUG, "ptk.size:%d\n", pkt->size);
fwrite(pkt->data, 1, pkt->size, out);
av_packet_unref(pkt);
}
_END:
return 0;
}
/*
* 对音频原始数据进行编码生成aac
* 本次音频数据是自己生成的声音数据
*/
int encode_audio(int argc, char* argv[]) {
int ret = -1;
FILE* f = NULL;
char* dst = NULL;
char* codecName = NULL;
const AVCodec* codec = NULL;
AVCodecContext* ctx = NULL;
AVFrame* frame = NULL;
AVPacket* pkt = NULL;
uint16_t* samples = NULL;
av_log_set_level(AV_LOG_DEBUG);
dst = "f:\\test_data\\encode_audio.aac";
//codecName = argv[2];
//2. 查找编码器
//codec = avcodec_find_encoder_by_name(codecName);
//codec = avcodec_find_encoder_by_name("libfdk_aac");
//codec = avcodec_find_encoder_by_name("libfaac");
codec = avcodec_find_encoder(AV_CODEC_ID_AAC);
if (!codec) {
av_log(NULL, AV_LOG_ERROR, "don't find Codec: %s", codecName);
goto _ERROR;
}
//3. 创建编码器上下文
ctx = avcodec_alloc_context3(codec);
if (!ctx) {
av_log(NULL, AV_LOG_ERROR, "NO MEMRORY\n");
goto _ERROR;
}
//4. 设置编码器参数 帧的宽高、帧率、gop这些对于音频都不太重要。
// 对于音频来说,重要的是采样大小、采样率、通道
// 一般是 16 32 64 128k
ctx->bit_rate = 64000;
//ctx->sample_fmt = AV_SAMPLE_FMT_S16;//AV_SAMPLE_FMT_FLTP 采样大小
ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;//AV_SAMPLE_FMT_FLTP 采样大小
if (!check_sample_fmt(codec, ctx->sample_fmt)) {
// 这个函数判断编码器支不支持采样大小。
av_log(NULL, AV_LOG_ERROR, "Encoder does not support sample format!\n");
goto _ERROR;
}
ctx->sample_rate = select_best_sample_rate(codec);
//av_channel_layout_copy(&ctx->ch_layout, &(AVChannelLayout)AV_CHANNEL_LAYOUT_STEREO); //AV_CHANNEL_LAYOUT_MONO
av_channel_layout_copy(&ctx->ch_layout, &(AVChannelLayout)AV_CHANNEL_LAYOUT_MONO); //AV_CHANNEL_LAYOUT_MONO
//5. 编码器与编码器上下文绑定到一起
ret = avcodec_open2(ctx, codec, NULL);
if (ret < 0) {
av_log(ctx, AV_LOG_ERROR, "Don't open codec: %s \n", av_err2str(ret));
goto _ERROR;
}
//6. 创建输出文件
f = fopen(dst, "wb");
if (!f) {
av_log(NULL, AV_LOG_ERROR, "Don't open file:%s", dst);
goto _ERROR;
}
//7. 创建AVFrame
frame = av_frame_alloc();
if (!frame) {
av_log(NULL, AV_LOG_ERROR, "NO MEMORY!\n");
goto _ERROR;
}
// 同样在为frame中的data分配空间的时候,告诉它一些参数信息
frame->nb_samples = ctx->frame_size;
frame->format = AV_SAMPLE_FMT_S16; //AV_SAMPLE_FMT_FLTP
av_channel_layout_copy(&frame->ch_layout, &(AVChannelLayout)AV_CHANNEL_LAYOUT_MONO); //AV_CHANNEL_LAYOUT_MONO
//av_channel_layout_copy(&frame->ch_layout, &(AVChannelLayout)AV_CHANNEL_LAYOUT_STEREO); //AV_CHANNEL_LAYOUT_MONO
frame->sample_rate = ctx->sample_rate;
ret = av_frame_get_buffer(frame, 0);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Could not allocate the video frame \n");
goto _ERROR;
}
//8. 创建AVPacket
pkt = av_packet_alloc();
if (!pkt) {
av_log(NULL, AV_LOG_ERROR, "NO MEMORY!\n");
goto _ERROR;
}
//9. 生成音频内容,和视频相似,都是以帧为单位,区别的地方:
// 音频的帧,它是流式的,也就是说我们在任何地点、任何位置都可以对这个数据进行切割。
// 切割成指定大小的一帧数据,对于视频来说,每一帧都是一张图像。
float t = 0;
float tincr = 4 * M_PI * 440 / ctx->sample_rate;
for (int i = 0; i < 200; i++) {
ret = av_frame_make_writable(frame);
if (ret < 0) {
av_log(NULL, AV_LOG_ERROR, "Could not allocate space!\n");
goto _ERROR;
}
// frame[0]是音频数据地址
samples = (uint16_t*)frame->data[0]; //FLTP 32 (uint32_t*)
for (int j = 0; j < ctx->frame_size; j++) {
samples[2 * j] = (int)(sin(t) * 10000); //4
for (int k = 1; k < ctx->ch_layout.nb_channels; k++) {
samples[2 * j + k] = samples[2 * j]; //4
}
t += tincr;
}
encode(ctx, frame, pkt, f);
}
//10. 编码
encode(ctx, NULL, pkt, f);
_ERROR:
//ctx
if (ctx) {
avcodec_free_context(&ctx);
}
//avframe
if (frame) {
av_frame_free(&frame);
}
//avpacket
if (pkt) {
av_packet_free(&pkt);
}
//dst
if (f) {
fclose(f);
}
return 0;
}