1.简介
编码音频数据,把pcm原始数据编码为MP3或者AAC。
2.流程
2.1在使用FFmpeg API之前,需要先注册API,然后才能使用API。当然,新版本的库不需要再调用下面的方法。
av_register_all()
2.2查找编码器,本例演示编码MP3
//找到编码器
codec = avcodec_find_encoder(AV_CODEC_ID_MP3);
if (!codec)
{
fprintf(stderr, "Codec not found\n");
exit(1);
}
2.3申请AVCodecContext
//申请AVCodecContext
AVCodecContext* codec_ctx = nullptr;
codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx)
{
fprintf(stderr, "Could not allocate audio codec context\n");
exit(1);
}
2.4设置音频参数
需要先指定配置编码器的一些参数。像音频的话,比如它的采样率,声道数,编码格式等。也需要配置编码后输出的数据的参数,如下,我们这里参数的设定是输入的PCM是48000,FLT,码率25600,输出的参数跟codec_ctx参数基本相似
//设置参数
codec_ctx->bit_rate = 256000;
codec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
//检查支不支持这个格式
if (!check_sample_fmt(codec, codec_ctx->sample_fmt))
{
fprintf(stderr, "Encoder does not support sample format %s",av_get_sample_fmt_name(codec_ctx->sample_fmt));
exit(1);
}
codec_ctx->sample_rate = 48000;
codec_ctx->channel_layout = select_channel_layout(codec);
codec_ctx->channels = av_get_channel_layout_nb_channels(codec_ctx->channel_layout);
frame->nb_samples = codec_ctx->frame_size;
frame->format = codec_ctx->sample_fmt;
frame->channel_layout = codec_ctx->channel_layout;
frame->channels = codec_ctx->channels;
2.5打开编码器
//打开编码器
if (avcodec_open2(codec_ctx, codec, NULL) < 0)
{
fprintf(stderr, "Could not open codec\n");
exit(1);
}
2.6分配帧空间
/* allocate the data buffers */
int ret = av_frame_get_buffer(frame, 0);
if (ret < 0)
{
fprintf(stderr, "Could not allocate audio data buffers\n");
exit(1);
}
2.7计算音频帧数据大小
设置完编码器的参数后和需要编码的数据帧相关的参数后,可以根据几个参数计算出音频采样buffer的大小。
//计算音频帧信息
int buffer_size = av_samples_get_buffer_size(NULL, codec_ctx->channels, codec_ctx->frame_size, codec_ctx->sample_fmt, 0);
if (buffer_size < 0)
{
exit(1);
}
2.8读取pcm文件,开始编码,写入到本地MP3文件。
准备就绪后,我们就可以开始编码。音频编码的新接口同样是通过发送给编码器,然后接收的方式进行编码。编码的核心代码如下,编码后的数据存放在AVPacket中,将pkt中的数据写入到本地文件即可。
while (!feof(fp))
{
//从文件读取数据
int count = fread(pcmBuffer, sizeof(char), buffer_size, fp);
//本地读取到的packed数据转换为planar
f32le_convert_to_fltp((float*)pcmBuffer, (float*)pcmBuffer2, frame->nb_samples);
ret = av_samples_fill_arrays(frame->data, frame->linesize,
(const uint8_t*)pcmBuffer2, frame->channels,
frame->nb_samples, AV_SAMPLE_FMT_FLTP, 0);
//开始编码
ret = avcodec_send_frame(codec_ctx, frame);
while (ret >= 0)
{
ret = avcodec_receive_packet(codec_ctx, pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
break;
}
else if (ret < 0)
{
break;
}
//编码后的数据写到本地文件
fwrite(pkt->data, 1, pkt->size, f);
av_packet_unref(pkt);
}
}
3.源码
本例演示从本地读取pcm文件,编码完成后写入到MP3文件中。
#include "pch.h"
#include <iostream>
extern "C"
{
#include "libavformat/avformat.h"
#include "libavutil/dict.h"
#include "libavutil/opt.h"
#include "libavutil/timestamp.h"
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
#include "libavutil/imgutils.h"
};
/* 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 f32le_convert_to_fltp(float* f32le, float* fltp, int nb_samples)
{
float* fltp_l = fltp; // 左通道
float* fltp_r = fltp + nb_samples; // 右通道
for (int i = 0; i < nb_samples; i++)
{
fltp_l[i] = f32le[i * 2]; // 0 1 - 2 3
fltp_r[i] = f32le[i * 2 + 1]; // 可以尝试注释左声道或者右声道听听声音
}
}
int main()
{
//av_register_all();
avformat_network_init();
AVCodec* codec = nullptr;
//找到编码器
codec = avcodec_find_encoder(AV_CODEC_ID_MP3);
if (!codec)
{
fprintf(stderr, "Codec not found\n");
exit(1);
}
//申请AVCodecContext
AVCodecContext* codec_ctx = nullptr;
codec_ctx = avcodec_alloc_context3(codec);
if (!codec_ctx)
{
fprintf(stderr, "Could not allocate audio codec context\n");
exit(1);
}
//设置参数
codec_ctx->bit_rate = 256000;
codec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
//检查支不支持这个格式
if (!check_sample_fmt(codec, codec_ctx->sample_fmt))
{
fprintf(stderr, "Encoder does not support sample format %s",av_get_sample_fmt_name(codec_ctx->sample_fmt));
exit(1);
}
codec_ctx->sample_rate = 48000;
codec_ctx->channel_layout = select_channel_layout(codec);
codec_ctx->channels = av_get_channel_layout_nb_channels(codec_ctx->channel_layout);
//打开编码器
if (avcodec_open2(codec_ctx, codec, NULL) < 0)
{
fprintf(stderr, "Could not open codec\n");
exit(1);
}
AVPacket *pkt = av_packet_alloc();
if (!pkt)
{
fprintf(stderr, "could not allocate the packet\n");
exit(1);
}
AVFrame *frame = av_frame_alloc();
if (!frame)
{
fprintf(stderr, "Could not allocate audio frame\n");
exit(1);
}
frame->nb_samples = codec_ctx->frame_size;
frame->format = codec_ctx->sample_fmt;
frame->channel_layout = codec_ctx->channel_layout;
frame->channels = codec_ctx->channels;
/* allocate the data buffers */
int ret = av_frame_get_buffer(frame, 0);
if (ret < 0)
{
fprintf(stderr, "Could not allocate audio data buffers\n");
exit(1);
}
//计算音频帧信息
int buffer_size = av_samples_get_buffer_size(NULL, codec_ctx->channels, codec_ctx->frame_size, codec_ctx->sample_fmt, 0);
if (buffer_size < 0)
{
exit(1);
}
uint8_t* pcmBuffer = (uint8_t*)av_malloc(buffer_size);
if (!pcmBuffer)
{
exit(1);
}
uint8_t* pcmBuffer2 = (uint8_t*)av_malloc(buffer_size);
if (!pcmBuffer2)
{
exit(1);
}
//打开输出的文件
char fileName[20] = "output.mp3";
FILE* f = fopen(fileName, "wb");
if (!f)
{
fprintf(stderr, "Could not open %s\n", fileName);
exit(1);
}
//打开输入文件
const char* inputUrl = "test.pcm";
FILE* fp = fopen(inputUrl, "rb");
if (!fp)
{
fprintf(stderr, "Could not open %s\n", inputUrl);
exit(1);
}
while (!feof(fp))
{
//从文件读取数据
int count = fread(pcmBuffer, sizeof(char), buffer_size, fp);
//本地读取到的packed数据转换为planar
f32le_convert_to_fltp((float*)pcmBuffer, (float*)pcmBuffer2, frame->nb_samples);
ret = av_samples_fill_arrays(frame->data, frame->linesize,
(const uint8_t*)pcmBuffer2, frame->channels,
frame->nb_samples, AV_SAMPLE_FMT_FLTP, 0);
//开始编码
ret = avcodec_send_frame(codec_ctx, frame);
while (ret >= 0)
{
ret = avcodec_receive_packet(codec_ctx, pkt);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
{
break;
}
else if (ret < 0)
{
break;
}
//编码后的数据写到本地文件
fwrite(pkt->data, 1, pkt->size, f);
av_packet_unref(pkt);
}
}
fclose(fp);
fclose(f);
avcodec_close(codec_ctx);
avcodec_free_context(&codec_ctx);
av_frame_free(&frame);
av_packet_free(&pkt);
return 0;
}