解題思路:
前文寫到了使用API接口將生成的純音PCM樣本直接寫入到.mp3文件中睹欲,我們是否可以使用同樣的方法生成.aac文件呢惕蹄?
答案是不行记舆,AAC文件格式要求寫入相應(yīng)的頭部箫荡,這一塊比較復(fù)雜说榆,具體請百度參閱相關(guān)文檔锥忿。
下面抄自網(wǎng)絡(luò):
有的時候當你編碼AAC裸流的時候牛郑,會遇到寫出來的AAC文件并不能在PC和手機上播放,很大的可能就是AAC文件的每一幀里缺少了ADTS頭信息文件的包裝拼接敬鬓。只需要加入頭文件ADTS即可淹朋。一個AAC原始數(shù)據(jù)塊長度是可變的笙各,對原始幀加上ADTS頭進行ADTS的封裝,就形成了ADTS幀础芍。
AAC音頻文件的每一幀由ADTS Header和AAC Audio Data組成杈抢。結(jié)構(gòu)體如下:
image.png
簡單可行的方法是,我們使用AVFormat的封裝器仑性,由封裝器幫我們做各種構(gòu)包的復(fù)雜工作惶楼。下面是我從網(wǎng)上抄的圖,它很好的演示了API的調(diào)用路線:
相關(guān)接口:
補充本文引入的封裝器操作相關(guān)的結(jié)構(gòu)和函數(shù)诊杆。
結(jié)構(gòu)介紹:
封裝與解封裝上下文:
typedef struct AVFormatContext
{
const struct AVInputFormat *iformat;
const struct AVOutputFormat *oformat;
AVIOContext *pb;
unsigned int nb_streams;
AVStream **streams;
int64_t start_time;
int64_t duration;
int64_t bit_rate;
AVDictionary *metadata;
} AVFormatContext;
媒體流的操作上下文:
typedef struct AVStream
{
int index; /**< stream index in AVFormatContext */
AVRational time_base;
int64_t start_time;
int64_t duration;
int64_t nb_frames;
AVRational r_frame_rate;
AVRational avg_frame_rate;
AVDictionary *metadata;
AVCodecParameters *codecpar;
};
編解碼器參數(shù)結(jié)構(gòu):
struct AVCodecParameters
{
enum AVMediaType codec_type;
enum AVCodecID codec_id;
uint32_t codec_tag;
/**
* - video: the pixel format, the value corresponds to enum AVPixelFormat.
* - audio: the sample format, the value corresponds to enum AVSampleFormat.
*/
int format;
/**
* The average bitrate of the encoded data (in bits per second).
*/
int64_t bit_rate;
int profile;
int level;
int width;
int height;
uint64_t channel_layout;
int channels;
int sample_rate;
};
媒體文件直接讀寫操作上下文:
typedef struct AVIOContext
{
unsigned char *buffer; /**< Start of the buffer. */
int buffer_size; /**< Maximum buffer size */
unsigned char *buf_ptr; /**< Current position in the buffer */
unsigned char *buf_end; /**< End of the data, may be less than
void *opaque; /**< A private pointer, passed to the read/write/seek/...functions. */
int (*read_packet)(void *opaque, uint8_t *buf, int buf_size);
int (*write_packet)(void *opaque, uint8_t *buf, int buf_size);
int64_t (*seek)(void *opaque, int64_t offset, int whence);
};
函數(shù)介紹:
分配輸出流上下文歼捐,format_name指定輸出文件格式,當format_name為NULL時晨汹,由filename的擴展名決定類型豹储。
int avformat_alloc_output_context2(AVFormatContext **ctx, const AVOutputFormat *oformat, const char *format_name, const char *filename);
釋放分配輸出流上下文。
void avformat_free_context(AVFormatContext *s);
根據(jù)s指定的上下文淘这,寫入文件頭剥扣。
int avformat_write_header(AVFormatContext *s, AVDictionary **options);
根據(jù)s指定的上下文,寫入壓縮包慨灭。
int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt);
根據(jù)s指定的上下文朦乏,寫入文件尾。
int av_write_trailer(AVFormatContext *s);
創(chuàng)建一個新的流氧骤。
AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c);
拷貝編解碼器的參數(shù)到流參數(shù)中呻疹。
int avcodec_parameters_from_context(AVCodecParameters *par, const AVCodecContext *codec);
打開媒體文件,url可以是磁盤文件筹陵,也可以是rtmp地址刽锤。
int avio_open(AVIOContext **s, const char *url, int flags);
關(guān)閉媒體文件。
int avio_closep(AVIOContext **s);
代碼舉例:
代碼的邏輯基本和前文生成MP3的一樣朦佩,主要加入了封裝器操作并思,如下:
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
extern "C"
{
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/channel_layout.h>
}
// 編碼幀
bool encode(AVCodecContext* pCodecCTX, const AVFrame* pFrame, AVPacket* pPacket, AVFormatContext* pOutputCTX, int& nPacketCount);
int main(int argc, char* argv[])
{
// 搜索指定的編碼器
const AVCodec* pCodec = avcodec_find_encoder_by_name("aac");
if (pCodec == NULL)
{
printf("not support aac encoder! \n");
return 0;
}
// 以下打印該編碼器支持的樣本格式、通道布局语稠、采樣率
printf("support sample formats: \n");
const enum AVSampleFormat* pSampleFMT = pCodec->sample_fmts;
while (pSampleFMT && *pSampleFMT)
{
printf("\t %d - %s \n", *pSampleFMT, av_get_sample_fmt_name(*pSampleFMT));
++pSampleFMT;
}
printf("support layouts: \n");
const uint64_t* pLayout = pCodec->channel_layouts;
while (pLayout && *pLayout)
{
int nb_channels = av_get_channel_layout_nb_channels(*pLayout);
char sBuf[128] = {0};
av_get_channel_layout_string(sBuf, sizeof(sBuf), nb_channels, *pLayout);
printf("\t %d - %s \n", nb_channels, sBuf);
++pLayout;
}
printf("support sample rates: \n");
const int* pSampleRate = pCodec->supported_samplerates;
while (pSampleRate && *pSampleRate)
{
printf("\t %dHz \n", *pSampleRate);
++pSampleRate;
}
// 根據(jù)編碼器初使化編碼器上下文結(jié)構(gòu)宋彼,并設(shè)置相關(guān)默認值
AVCodecContext* pCodecCTX = avcodec_alloc_context3(pCodec);
if (pCodecCTX == NULL)
{
printf("alloc aac encoder context failed! \n");
return 0;
}
// 填寫音頻編碼的關(guān)鍵參數(shù):樣本格式、通道數(shù)及布局仙畦、采樣率
pCodecCTX->sample_fmt = AV_SAMPLE_FMT_FLTP;
pCodecCTX->channel_layout = AV_CH_LAYOUT_STEREO;
pCodecCTX->channels = 2;
pCodecCTX->sample_rate = 44100;
// 以設(shè)定的參數(shù)打開編碼器
int rc = avcodec_open2(pCodecCTX, pCodec, NULL);
if (rc < 0)
{
char sError[128] = {0};
av_strerror(rc, sError, sizeof(sError));
printf("avcodec_open2() ret:[%d:%s] \n", rc, sError);
return -1;
}
AVPacket* pPacket = av_packet_alloc();
AVFrame* pFrame = av_frame_alloc();
// 設(shè)置音頻幀的相關(guān)參數(shù):樣本格式输涕、通道數(shù)及布局、樣本數(shù)量
pFrame->format = pCodecCTX->sample_fmt;
pFrame->channel_layout = pCodecCTX->channel_layout;
pFrame->channels = pCodecCTX->channels;
pFrame->nb_samples = pCodecCTX->frame_size;
// 根據(jù)參數(shù)設(shè)置慨畸,申請幀空間
rc = av_frame_get_buffer(pFrame, 0);
if (rc < 0)
{
char sError[128] = {0};
av_strerror(rc, sError, sizeof(sError));
printf("avcodec_open2() ret:[%d:%s] \n", rc, sError);
return -1;
}
// 這里打開輸出文件
AVFormatContext* pOutputCTX = NULL;
rc = avformat_alloc_output_context2(&pOutputCTX, NULL, NULL, "test.aac");
if (rc < 0)
{
char sError[128] = {0};
av_strerror(rc, sError, sizeof(sError));
printf("avformat_alloc_output_context2() ret:[%d:%s] \n", rc, sError);
return -1;
}
// 創(chuàng)建一路輸出流
AVStream* pStreamOut = avformat_new_stream(pOutputCTX, NULL);
avcodec_parameters_from_context(pStreamOut->codecpar, pCodecCTX);
// 打開輸出文件
rc = avio_open(&pOutputCTX->pb, "test.aac", AVIO_FLAG_WRITE);
if (rc < 0)
{
char sError[128] = {0};
av_strerror(rc, sError, sizeof(sError));
printf("avio_open() ret:[%d:%s] \n", rc, sError);
return -1;
}
// 寫入輸出文件頭
rc = avformat_write_header(pOutputCTX, NULL);
if (rc < 0)
{
char sError[128] = {0};
av_strerror(rc, sError, sizeof(sError));
printf("avformat_write_header() ret:[%d:%s] \n", rc, sError);
return -1;
}
// 當前已經(jīng)處理的包數(shù)
int nPacketCount = 0;
// 計算1Khz的正弦波采樣步進
float fXStep = 2 * 3.1415926 * 1000 / pCodecCTX->sample_rate;
for (int n = 0; n < pCodecCTX->sample_rate * 10; )
{
av_frame_make_writable(pFrame);
// 下面以平面格式進行音頻格式組裝
for (int c = 0; c < pFrame->channels; ++c)
{
float* pData = (float*)pFrame->data[c];
for (int i = 0; i < pFrame->nb_samples; ++i)
{
pData[i] = sin(fXStep * (n + i)) * 1000;
}
}
// 編碼生成壓縮格式
if (!encode(pCodecCTX, pFrame, pPacket, pOutputCTX, nPacketCount))
{
printf("encode() fatal! \n");
exit(-1);
}
n += pFrame->nb_samples;
}
// 寫入尾包
encode(pCodecCTX, NULL, pPacket, pOutputCTX, nPacketCount);
av_write_trailer(pOutputCTX);
printf("test.aac write succuss! \n");
avio_closep(&pOutputCTX->pb);
avformat_free_context(pOutputCTX);
av_frame_free(&pFrame);
av_packet_free(&pPacket);
avcodec_free_context(&pCodecCTX);
return 0;
}
// 編碼幀
bool encode(AVCodecContext* pCodecCTX, const AVFrame* pFrame, AVPacket* pPacket, AVFormatContext* pOutputCTX, int& nPacketCount)
{
int rc = avcodec_send_frame(pCodecCTX, pFrame);
if (rc < 0)
{
char sError[128] = {0};
av_strerror(rc, sError, sizeof(sError));
printf("avcodec_send_frame() ret:[%d:%s] \n", rc, sError);
return false;
}
while (true)
{
rc = avcodec_receive_packet(pCodecCTX, pPacket);
if (rc < 0)
{
if (rc == AVERROR(EAGAIN) || rc == AVERROR_EOF)
return true;
char sError[128] = {0};
av_strerror(rc, sError, sizeof(sError));
printf("avcodec_receive_packet() ret:[%d:%s] \n", rc, sError);
return false;
}
// 指定流索引
pPacket->stream_index = 0;
// 對音頻來說莱坎,簡單補充PTS、DTS
pPacket->dts = pPacket->pts = nPacketCount++;
av_interleaved_write_frame(pOutputCTX, pPacket);
av_packet_unref(pPacket);
}
return true;
}
編譯:
g++ -o encode_aac encode_aac.cpp -I/usr/local/ffmpeg/include -L/usr/local/ffmpeg/lib -lavformat -lavcodec -lavutil
運行寸士,輸出如下:
$ ./encode_aac
support sample formats:
8 - fltp
-1 - (null)
3145826 - (null)
support layouts:
support sample rates:
96000Hz
88200Hz
64000Hz
48000Hz
44100Hz
32000Hz
24000Hz
22050Hz
16000Hz
12000Hz
11025Hz
8000Hz
7350Hz
test.aac write succuss!
[aac @ 0000026156518500] Qavg: 1729.335
如果輸出如下如下的信息:
[adts @ 000002126e08cb40] Encoder did not produce proper pts, making some up.
這里表示寫入AAC文件時要求我們填寫PTS檐什,但對音頻來說碴卧,PTS和DTS并不要求絕對正確,通常只要做到自增即可乃正。
查看test.aac媒體信息:
$ ffprobe -i test.aac
ffprobe version N-104465-g08a501946f Copyright (c) 2007-2021 the FFmpeg developers
built with gcc 11.2.0 (Rev6, Built by MSYS2 project)
configuration: --prefix=/usr/local/ffmpeg --enable-shared --enable-libmp3lame
libavutil 57. 7.100 / 57. 7.100
libavcodec 59. 12.100 / 59. 12.100
libavformat 59. 8.100 / 59. 8.100
libavdevice 59. 0.101 / 59. 0.101
libavfilter 8. 16.101 / 8. 16.101
libswscale 6. 1.100 / 6. 1.100
libswresample 4. 0.100 / 4. 0.100
[aac @ 0000029e6d19cbc0] Estimating duration from bitrate, this may be inaccurate
Input #0, aac, from 'test.aac':
Duration: 00:00:10.09, bitrate: 129 kb/s
Stream #0:0: Audio: aac (LC), 44100 Hz, stereo, fltp, 129 kb/s