1. 程式人生 > >FFMPEG實現PCM編碼(不採用封裝格式實現)

FFMPEG實現PCM編碼(不採用封裝格式實現)

技術在於交流、溝通,轉載請註明出處並保持作品的完整性。

原文:https://blog.csdn.net/hiwubihe/article/details/81260923

 

[音訊編解碼系列文章]

  1. 音訊編解碼基礎
  2. FFMPEG實現音訊重取樣
  3. FFMPEG實現PCM編碼(採用封裝格式實現)
  4. FFMPEG實現PCM編碼(不採用封裝格式實現)
  5. FAAC庫實現PCM編碼
  6. FAAD庫實現RAW格式AAC解碼
  7. FAAD庫實現RAW格式AAC封裝成ADTS格式
  8. FAAD庫實現ADTS格式解碼
  9. FFMPEG實現對AAC解碼(採用封裝格式實現)
  10. 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;

}