最简单的基于 FFmpeg 的音频编码器(PCM 编码为 AAC)
- 最简单的基于 FFmpeg 的音频编码器(PCM 编码为 AAC)
- 正文
- 结果
- 工程文件下载
- 其他参考链接
最简单的基于 FFmpeg 的音频编码器(PCM 编码为 AAC)
参考雷霄骅博士的文章,链接:最简单的基于FFMPEG的音频编码器(PCM编码为AAC)
正文
本文介绍一个最简单的基于 FFmpeg 的音频编码器。
该编码器实现了 PCM 音频采样数据编码为 AAC 的压缩编码数据。
下面附一张使用 FFmpeg 编码音频的流程图。使用该流程,不仅可以编码 AAC 的音频,而且可以编码 MP3,MP2 等等各种 FFmpeg 支持的音频。图中蓝色背景的函数是实际输出数据的函数。浅绿色的函数是音频编码的函数。
简单介绍一下流程中各个函数的意义:
- av_register_all():注册 FFmpeg 所有编解码器。
- avformat_alloc_output_context2():初始化输出码流的 AVFormatContext。
- avio_open():打开输出文件。
- av_new_stream():创建输出码流的 AVStream。
- avcodec_find_encoder():查找编码器。
- avcodec_open2():打开编码器。
- avformat_write_header():写文件头(对于某些没有文件头的封装格式,不需要此函数。比如说 MPEG2TS)。
- avcodec_encode_audio2():编码音频。即将 AVFrame(存储 PCM 采样数据)编码为 AVPacket(存储 AAC,MP3 等格式的码流数据)。
- av_write_frame():将编码后的视频码流写入文件。
- av_write_trailer():写文件尾(对于某些没有文件头的封装格式,不需要此函数。比如说 MPEG2TS)。
另外,程序中的函数 flush_encoder():输入的像素数据读取完成后调用此函数。用于输出编码器中剩余的 AVPacket。
编码器代码十分简单,但是每一行代码都很重要。通过看本编码器的源代码,可以了解 FFmpeg 音频编码的流程。
本程序使用编译时间为 2014.5.6 的 FFmpeg 类库,下载链接:【免费】FFmpeg 库.zip。
开发平台为 VC2015。所有的配置都已经做好,只需要运行就可以了。
源代码:
// Simplest FFmpeg Audio Encoder.cpp : 定义控制台应用程序的入口点。
///**
* 最简单的基于 FFmpeg 的音频编码器
* Simplest FFmpeg Audio Encoder
*
* 源程序:
* 雷霄骅 Lei Xiaohua
* leixiaohua1020@126.com
* 中国传媒大学/数字电视技术
* Communication University of China / Digital TV Technology
* http://blog.csdn.net/leixiaohua1020
*
* 修改:
* 刘文晨 Liu Wenchen
* 812288728@qq.com
* 电子科技大学/电子信息
* University of Electronic Science and Technology of China / Electronic and Information Science
* https://blog.csdn.net/ProgramNovice
*
* 本程序实现了音频 PCM 采样数据编码为压缩码流(MP3,WMA,AAC 等)。
* 是最简单的 FFmpeg 音频编码方面的教程。
* 通过学习本例子可以了解 FFmpeg 的编码流程。
*
* This software encode PCM data to AAC bitstream.
* It's the simplest audio encoding software based on FFmpeg.
* Suitable for beginner of FFmpeg
*
*/#include "stdafx.h"#include <stdio.h>// 解决报错:fopen() 函数不安全
#pragma warning(disable:4996)// 解决报错:无法解析的外部符号 __imp__fprintf,该符号在函数 _ShowError 中被引用
#pragma comment(lib, "legacy_stdio_definitions.lib")
extern "C"
{// 解决报错:无法解析的外部符号 __imp____iob_func,该符号在函数 _ShowError 中被引用FILE __iob_func[3] = { *stdin, *stdout, *stderr };
}#define __STDC_CONSTANT_MACROS#ifdef _WIN32
// Windows
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
};
#else
// Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#ifdef __cplusplus
};
#endif
#endifint flush_encoder(AVFormatContext *fmt_ctx, unsigned int stream_index)
{int ret;int got_frame;AVPacket enc_pkt;if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &CODEC_CAP_DELAY))return 0;while (1){enc_pkt.data = NULL;enc_pkt.size = 0;av_init_packet(&enc_pkt);ret = avcodec_encode_audio2(fmt_ctx->streams[stream_index]->codec, &enc_pkt,NULL, &got_frame);av_frame_free(NULL);if (ret < 0)break;if (!got_frame){ret = 0;break;}printf("Flush Encoder: Succeed to encode 1 frame!\tsize:%5d.\n", enc_pkt.size);// mux encoded frameret = av_write_frame(fmt_ctx, &enc_pkt);if (ret < 0){break;}}return ret;
}int main(int argc, char* argv[])
{AVFormatContext* pFormatCtx;AVOutputFormat* fmt;AVStream* audio_stream;AVCodecContext* pCodecCtx;AVCodec* pCodec;uint8_t* frame_buf;AVFrame* pFrame;AVPacket pkt;int got_frame = 0;int size = 0;int ret = 0;FILE *fp_in = fopen("tdjm.pcm", "rb"); // 输入 PCM 文件int framenum = 1000; // Audio frame numberconst char* out_file = "tdjm.aac"; // 输出 URLint i;av_register_all();// Method 1// pFormatCtx = avformat_alloc_context();// fmt = av_guess_format(NULL, out_file, NULL);// pFormatCtx->oformat = fmt;// Method 2 (More simple)avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, out_file);fmt = pFormatCtx->oformat;// Open Output URLif (avio_open(&pFormatCtx->pb, out_file, AVIO_FLAG_READ_WRITE) < 0){printf("Can't open output file.\n");return -1;}audio_stream = avformat_new_stream(pFormatCtx, 0);if (audio_stream == NULL){printf("Can't create audio stream.\n");return -1;}pCodecCtx = audio_stream->codec;pCodecCtx->codec_id = fmt->audio_codec;pCodecCtx->codec_type = AVMEDIA_TYPE_AUDIO;pCodecCtx->sample_fmt = AV_SAMPLE_FMT_S16;pCodecCtx->sample_rate = 44100;pCodecCtx->channel_layout = AV_CH_LAYOUT_STEREO;pCodecCtx->channels = av_get_channel_layout_nb_channels(pCodecCtx->channel_layout);pCodecCtx->bit_rate = 64000;//pCodec = avcodec_find_encoder(fmt->audio_codec);//if (!pCodec)//{// printf("Can't find encoder.\n");// return -1;//}//pCodecCtx = audio_stream->codec;//pCodecCtx->codec_id = fmt->audio_codec;//pCodecCtx->codec_type = AVMEDIA_TYPE_AUDIO;//pCodecCtx->sample_fmt = pCodec->sample_fmts[0];//pCodecCtx->sample_rate = 44100;//pCodecCtx->channel_layout = AV_CH_LAYOUT_STEREO;//pCodecCtx->channels = av_get_channel_layout_nb_channels(pCodecCtx->channel_layout);//pCodecCtx->bit_rate = 64000;//pCodecCtx->profile = FF_PROFILE_AAC_MAIN;//pCodecCtx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;// Print some informationav_dump_format(pFormatCtx, 0, out_file, 1);pCodec = avcodec_find_encoder(pCodecCtx->codec_id);if (!pCodec){printf("Can not find encoder!\n");return -1;}if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0){printf("Failed to open encoder.\n");return -1;}pFrame = av_frame_alloc();pFrame->nb_samples = pCodecCtx->frame_size;pFrame->format = pCodecCtx->sample_fmt;// 计算缓存区域大小size = av_samples_get_buffer_size(NULL, pCodecCtx->channels, pCodecCtx->frame_size, pCodecCtx->sample_fmt, 1);// 分配缓存frame_buf = (uint8_t *)av_malloc(size);if (!frame_buf){printf("Can't malloc frame buffer.\n");return -1;}avcodec_fill_audio_frame(pFrame, pCodecCtx->channels, pCodecCtx->sample_fmt,(const uint8_t*)frame_buf, size, 1);// Write Headeravformat_write_header(pFormatCtx, NULL);av_new_packet(&pkt, size);for (i = 0; i < framenum; i++){// Read PCMif (fread(frame_buf, sizeof(char), size, fp_in) <= 0){printf("Failed to read raw data! \n");return -1;}else if (feof(fp_in)){break;}pFrame->data[0] = frame_buf; // PCM DatapFrame->pts = i * 100;got_frame = 0;// Encoderet = avcodec_encode_audio2(pCodecCtx, &pkt, pFrame, &got_frame);if (ret < 0){printf("Failed to encode!\n");return -1;}if (got_frame == 1){printf("Succeed to encode 1 frame! \tsize:%5d\n", pkt.size);pkt.stream_index = audio_stream->index;ret = av_write_frame(pFormatCtx, &pkt);av_free_packet(&pkt);}}// Flush Encoderret = flush_encoder(pFormatCtx, 0);if (ret < 0){printf("Flushing encoder failed.\n");return -1;}// Write Trailerav_write_trailer(pFormatCtx);printf("Encode Successful.\n");// Cleanif (audio_stream){avcodec_close(audio_stream->codec);av_free(pFrame);av_free(frame_buf);}avio_close(pFormatCtx->pb);avformat_free_context(pFormatCtx);fclose(fp_in);return 0;
}
结果
输入 pcm 文件:
采样率 64000,双声道,位深 16bit。
在 Adobe Audition 2020 中查看波形:
运行程序,得到采样率为 44100HZ 的输出文件 tdjm.aac。
用 MediaInfo 查看:
播放该 aac 文件,能听出来是周杰伦的《她的睫毛》。
注:pcm 也能播放,因为采样率是 64000HZ,比正常速度的 44100HZ 快,所以听着有些奇怪。
工程文件下载
GitHub:UestcXiye / Simplest-FFmpeg-Audio-Encoder
CSDN:Simplest FFmpeg Audio Encoder.zip
其他参考链接
- 新版ffmpeg编码AAC注意事项
- FFmpeg 音频编码