FFMPEG實現PCM編碼(不採用封裝格式實現)
技術在於交流、溝通,轉載請註明出處並保持作品的完整性。
原文:https://blog.csdn.net/hiwubihe/article/details/81260923
[音訊編解碼系列文章]
- 音訊編解碼基礎
- FFMPEG實現音訊重取樣
- FFMPEG實現PCM編碼(採用封裝格式實現)
- FFMPEG實現PCM編碼(不採用封裝格式實現)
- FAAC庫實現PCM編碼
- FAAD庫實現RAW格式AAC解碼
- FAAD庫實現RAW格式AAC封裝成ADTS格式
- FAAD庫實現ADTS格式解碼
- FFMPEG實現對AAC解碼(採用封裝格式實現)
- FFMPEG實現對AAC解碼(不採用封裝格式實現)
在上篇“FFMPEG實現PCM編碼(採用封裝格式實現)”實現了通過FFMPEG對PCM資料編碼,實現上把AAC作為一種封裝格式實現的。本篇將採用一種更加簡潔的方式,只用FFMPEG中的編碼庫,不用封裝庫實現PCM編碼成AAC,當然這種前提是對AAC的格式需要了解。AAC有原始格式,ADTS和ADIF格式,原始格式一般解碼不出來,因為沒有音訊相關的引數資訊,後面介紹FAAD庫的使用時,可以知道這種格式可以手動的把引數傳輸給FAAD庫,完成解碼。一般的播放器如vlc是解碼不了這種音訊的。本篇將首先讀取一幀PCM資料,然後送給編碼器,編碼器完成一幀的編碼後,在每一幀前面加上ADTS頭,從而完成把PCM編碼成ADTS格式的AAC檔案。
ADTS格式標準"ISO-IEC-13818-7",可以參考。格式主要有下面表格
Table 8 – Syntax of adts_fixed_header()
Syntax |
No. of bits |
Mnemonic |
adts_fixed_header() |
|
|
{ |
|
|
syncword; |
12 |
bslbf |
ID; |
1 |
bslbf |
layer; |
2 |
uimsbf |
protection_absent; |
1 |
bslbf |
profile; |
2 |
uimsbf |
sampling_frequency_index; |
4 |
uimsbf |
private_bit; |
1 |
bslbf |
channel_configuration; |
3 |
uimsbf |
original/copy; |
1 |
bslbf |
home; |
1 |
bslbf |
} |
|
|
Table 9 – Syntax of adts_variable_header()
Syntax |
No. of bits |
Mnemonic |
adts_variable_header() |
|
|
{ |
|
|
copyright_identification_bit; |
1 |
bslbf |
copyright_identification_start; |
1 |
bslbf |
frame_length; |
13 |
bslbf |
adts_buffer_fullness; |
11 |
bslbf |
number_of_raw_data_blocks_in_frame; |
2 |
uimsfb |
} |
|
|
幀資料加入ADTS頭程式碼
unsigned bool MakeAdtsHeader(unsigned char *pHead,int *HeadSize,int iProfile,int iSampleRate,int iChan ,int iFramelen)
{
unsigned char *data = pHead;
int iHeadLen = *HeadSize;
if(iHeadLen<7)
{
return false;
}
*HeadSize = iHeadLen = 7;
int profile = (iProfile - 1) & 0x3;
int sr_index = FindAdtsSRIndex(iSampleRate);
int framesize = iFramelen + iHeadLen;
memset(data, 0, iHeadLen);
data[0] += 0xFF; /* 8b: syncword */
data[1] += 0xF0; /* 4b: syncword */
/* 1b: mpeg id = 0 */
/* 2b: layer = 0 */
data[1] += 1; /* 1b: protection absent */
data[2] += ((profile << 6) & 0xC0); /* 2b: profile */
data[2] += ((sr_index << 2) & 0x3C); /* 4b: sampling_frequency_index */
/* 1b: private = 0 */
data[2] += ((iChan >> 2) & 0x1); /* 1b: channel_configuration */
data[3] += ((iChan << 6) & 0xC0); /* 2b: channel_configuration */
/* 1b: original */
/* 1b: home */
/* 1b: copyright_id */
/* 1b: copyright_id_start */
data[3] += ((framesize >> 11) & 0x3); /* 2b: aac_frame_length */
data[4] += ((framesize >> 3) & 0xFF); /* 8b: aac_frame_length */
data[5] += ((framesize << 5) & 0xE0); /* 3b: aac_frame_length */
data[5] += ((0x7FF >> 6) & 0x1F); /* 5b: adts_buffer_fullness */
data[6] += ((0x7FF << 2) & 0x3F); /* 6b: adts_buffer_fullness */
/* 2b: num_raw_data_blocks */
return true;
}
DEMO例項程式碼
/*******************************************************************************
Copyright (c) wubihe Tech. Co., Ltd. All rights reserved.
--------------------------------------------------------------------------------
Date Created: 2014-10-25
Author: wubihe QQ:1269122125 Email:[email protected]
Description: 程式碼實現PCM編碼AAC 不採用封裝格式
--------------------------------------------------------------------------------
Modification History
DATE AUTHOR DESCRIPTION
--------------------------------------------------------------------------------
********************************************************************************/
#include <stdio.h>
#define __STDC_CONSTANT_MACROS
#ifdef _WIN32
//Windows
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswresample/swresample.h"
};
#else
//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#ifdef __cplusplus
};
#endif
#endif
#define INPUT_FILE_NAME ("huangdun_r48000_FMT_S16_c2.pcm")
#define ADTS_HEAD_LEN (7)
//AAC的編碼級別 profile
#define MAIN 1
#define LC 2
#define SSR 3
#define LTP 4
#define HE_AAC 5
#define ER_LC 17
#define ER_LTP 19
#define LD 23
#define DRM_ER_LC 27 /* special object type for DRM */
static int adts_sample_rates[] = {96000,88200,64000,48000,44100,32000,24000,22050,16000,12000,11025,8000,7350,0,0,0};
static int FindAdtsSRIndex(int sr)
{
int i;
for (i = 0; i < 16; i++)
{
if (sr == adts_sample_rates[i])
return i;
}
return 16 - 1;
}
unsigned bool MakeAdtsHeader(unsigned char *pHead,int *HeadSize,int iProfile,int iSampleRate,int iChan ,int iFramelen)
{
unsigned char *data = pHead;
int iHeadLen = *HeadSize;
if(iHeadLen<7)
{
return false;
}
*HeadSize = iHeadLen = 7;
int profile = (iProfile - 1) & 0x3;
int sr_index = FindAdtsSRIndex(iSampleRate);
int framesize = iFramelen + iHeadLen;
memset(data, 0, iHeadLen);
data[0] += 0xFF; /* 8b: syncword */
data[1] += 0xF0; /* 4b: syncword */
/* 1b: mpeg id = 0 */
/* 2b: layer = 0 */
data[1] += 1; /* 1b: protection absent */
data[2] += ((profile << 6) & 0xC0); /* 2b: profile */
data[2] += ((sr_index << 2) & 0x3C); /* 4b: sampling_frequency_index */
/* 1b: private = 0 */
data[2] += ((iChan >> 2) & 0x1); /* 1b: channel_configuration */
data[3] += ((iChan << 6) & 0xC0); /* 2b: channel_configuration */
/* 1b: original */
/* 1b: home */
/* 1b: copyright_id */
/* 1b: copyright_id_start */
data[3] += ((framesize >> 11) & 0x3); /* 2b: aac_frame_length */
data[4] += ((framesize >> 3) & 0xFF); /* 8b: aac_frame_length */
data[5] += ((framesize << 5) & 0xE0); /* 3b: aac_frame_length */
data[5] += ((0x7FF >> 6) & 0x1F); /* 5b: adts_buffer_fullness */
data[6] += ((0x7FF << 2) & 0x3F); /* 6b: adts_buffer_fullness */
/* 2b: num_raw_data_blocks */
return true;
}
int main()
{
int iReturn = 0;
int iAdtsHeadLen = ADTS_HEAD_LEN;
unsigned char szAdtsHead[ADTS_HEAD_LEN];
int iAacProfile = LC;
//各種不同格式對應位元組數
static int mapSampleBytes[AV_SAMPLE_FMT_NB]
={1,2,4,4,8,1,2,4,4,8};
//PCM原始資料格式
uint64_t iInputLayout = AV_CH_LAYOUT_STEREO;
int iInputChans = av_get_channel_layout_nb_channels(iInputLayout);
AVSampleFormat eInputSampleFormat = AV_SAMPLE_FMT_S16;
int iInputSampleRate = 48000;
//不同樣本格式長度
int iInputSampleBytes = mapSampleBytes[eInputSampleFormat];
//註冊所有編解碼器
avcodec_register_all();
//開啟編碼器
AVCodec *pCodec = NULL;
AVCodecContext *pCodecCtx = NULL;
pCodec = avcodec_find_encoder(AV_CODEC_ID_AAC);
if (!pCodec)
{
printf("Codec not found\n");
return -1;
}
pCodecCtx = avcodec_alloc_context3(pCodec);
if (!pCodecCtx)
{
printf("Could not allocate video codec context\n");
return -1;
}
//音訊編碼 不同格式支援不同的PCM原始資料,如果資料格式不同就需要重取樣
//如PCM編碼MP3格式的音訊檔案,原始PCM是交叉儲存方式則需要重取樣,因為MP3編碼器不支援
//具體反應在avcodec_open2會失敗 下面設定是告訴編碼器PCM的格式,如果PCM與下面設定不同需要重取樣
pCodecCtx->codec_id = AV_CODEC_ID_AAC;
pCodecCtx->codec_type = AVMEDIA_TYPE_AUDIO;
pCodecCtx->sample_fmt = eInputSampleFormat;
pCodecCtx->sample_rate = iInputSampleRate;
pCodecCtx->channel_layout = iInputLayout;
pCodecCtx->channels = iInputChans;
if ((iReturn = avcodec_open2(pCodecCtx, pCodec, NULL)) < 0)
{
printf("Could not open codec\n");
return -1;
}
//開啟輸入檔案
FILE *pInputFile = fopen(INPUT_FILE_NAME, "rb");
if(pInputFile == NULL)
{
printf("Could not open File:%s \n",INPUT_FILE_NAME);
return -1;
}
//開啟輸出檔案
FILE *pOutputFile = fopen("huangdun.aac", "wb");
if(pInputFile == NULL)
{
printf("Could not open File:%s \n","huangdun.aac");
return -1;
}
//1幀資料樣本數(AAC單通道1024)
int iFrameSamples = 1024;
//原始資料幀
AVFrame* pRawframe = NULL;
//原始幀一Planer的大小 非平面分佈的情況就是快取總大小
int iRawLineSize = 0;
//原始幀快取大小
int iRawBuffSize = 0;
//原始幀快取
uint8_t *pRawBuff= NULL;
// 儲存原始資料
iRawLineSize = 0;
iRawBuffSize = av_samples_get_buffer_size(&iRawLineSize, iInputChans, iFrameSamples, eInputSampleFormat, 0);
pRawBuff = (uint8_t *)av_malloc(iRawBuffSize);
//原始資料儲存在AVFrame結構體中
pRawframe = av_frame_alloc();
pRawframe->nb_samples = iFrameSamples;
pRawframe->format = eInputSampleFormat;
pRawframe->channels = iInputChans;
iReturn = avcodec_fill_audio_frame(pRawframe, iInputChans, eInputSampleFormat, (const uint8_t*)pRawBuff, iRawBuffSize, 0);
if(iReturn<0)
{
return -1;
}
//編碼以後的資料是AVPacket
AVPacket pkt;
av_new_packet(&pkt,iRawBuffSize);
//統計讀取樣本數
long long lReadTotalSamples = 0;
//每次讀取樣本數
int iReadSamples;
//是否編碼成功
int got_frame =0;
//讀取資料 儲存在pConvertframe->data
int iRealRead = fread(pRawBuff, 1, iRawBuffSize, pInputFile);
while(iRealRead>0)
{
iReadSamples = iRealRead/(iInputSampleBytes*iInputChans);
pRawframe->pts = lReadTotalSamples;
got_frame = 0;
//Encode
if(avcodec_encode_audio2(pCodecCtx, &pkt,pRawframe, &got_frame)<0)
{
printf("Failed to encode!\n");
return -1;
}
if (got_frame==1)
{
printf("Succeed to encode 1 frame! \tsize:%5d\n",pkt.size);
//一幀前面頭ADTS
MakeAdtsHeader(szAdtsHead,&iAdtsHeadLen,iAacProfile,iInputSampleRate,iInputChans ,pkt.size);
fwrite(szAdtsHead, iAdtsHeadLen, 1, pOutputFile);
fwrite(pkt.data, 1, pkt.size, pOutputFile);
//解碼資料寫檔案
av_free_packet(&pkt);
}
//統計樣本數以轉換前為準 轉換前後樣本數是一樣的
lReadTotalSamples += (iReadSamples);
iRealRead = fread(pRawBuff, 1, iRawBuffSize, pInputFile);
}
//重新整理解碼快取
//Flush Encoder
got_frame = 1;
while(got_frame)
{
if(avcodec_encode_audio2(pCodecCtx, &pkt, NULL, &got_frame)<0)
{
printf("Failed to encode!\n");
return -1;
}
if (got_frame)
{
MakeAdtsHeader(szAdtsHead,&iAdtsHeadLen,iAacProfile,iInputSampleRate,iInputChans ,pkt.size);
fwrite(szAdtsHead, iAdtsHeadLen, 1, pOutputFile);
fwrite(pkt.data, 1, pkt.size, pOutputFile);
//輸出檔案
av_free_packet(&pkt);
}
}
fclose(pInputFile);
fclose(pOutputFile);
avcodec_close(pCodecCtx);
av_free(pCodecCtx);
av_frame_free(&pRawframe);
return 0;
}