FFmpeg 音频编码(PCM数据编码成AAC android)

以前作的直播设计到音视频编码、rtmp推流、rtmp流播放等内容,如今抽时间整理一下。首先说一下音频编码(pcm 编码获得aac数据)。html

                                                                                                                                                                                     

1 录音(获取pcm数据)

开始录音java

private void startRecord(){

	Log.i(TAG, "startRecord mIsRecording="+mIsRecording);
	if(!mIsRecording){
		mIsRecording = true;
		synchronized (mLock) {
			mAudioRecordGetExit = false;
		}
		//初始化ffmpeg 编码器
		mFFAacEncoderJni.start();
		//建立录音线程、开始录音
		mAudioRecordGetThread = new Thread(new AudioRecordGet());
		mAudioRecordGetThread.start();
	}
}
关闭录音

private void stoptRecord(){
	if(mIsRecording){
		synchronized (mLock) {
			mAudioRecordGetExit = true;
		}
		mIsRecording = false;
	}
}


具体录音经过使用AudioRecord
private class AudioRecordGet implements Runnable{
	private AudioRecord mAudioRecord;
	private static final boolean PCM_DUMP_DEBUG = true;
	private static final boolean AAC_DUMP_DEBUG = false;
	private int mAudioSource = MediaRecorder.AudioSource.MIC;
	//采样频率,采样频率越高,音质越好。44100 、22050、 8000、4000等
	private int mSampleRateHz = 8000;
	//MONO为单声道 ,STEREO为双声道
	private int mChannelConfig = AudioFormat.CHANNEL_IN_MONO;
	//编码格式和采样大小,pcm编码;支持的采样大小16bit和8bit,采样大小越大,信息越多,音质越好。
	private int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
	//该size设置为AudioRecord.getMinBufferSize(mSampleRateHz, mChannelConfig, mAudioFormat); 编码aac时会失败。
	private int mBufferSizeInBytes = 2048;//AudioRecord.getMinBufferSize(mSampleRateHz, mChannelConfig, mAudioFormat);
	private AudioPCMData mAudioPCMData;
	public AudioRecordGet() {
		Log.i(TAG, "AudioRecordGet ");
		mAudioPCMData = new AudioPCMData(mBufferSizeInBytes);
		mAudioRecord = new AudioRecord(mAudioSource,
				mSampleRateHz, mChannelConfig, mAudioFormat, mBufferSizeInBytes);
		Log.i(TAG,"mBufferSizeInBytes="+mBufferSizeInBytes);
	}
	@Override
	public void run() {
		mAudioRecord.startRecording();
		FileOutputStream outPCM = null;
		try {
			if (PCM_DUMP_DEBUG) {
				String File = "/sdcard/test.pcm";
				outPCM = new FileOutputStream(File);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		for(;;){
			synchronized (mLock) {
				if(mAudioRecordGetExit){
					break;
				}
			}
			//读取录音数据
			int readSize = mAudioRecord.read(mAudioPCMData.mData, 0, mBufferSizeInBytes);
			if (AudioRecord.ERROR_INVALID_OPERATION != readSize) {
				if (PCM_DUMP_DEBUG && null != outPCM) {
					try {
						outPCM.write(mAudioPCMData.mData, 0, readSize);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
				mAudioPCMData.mFrameSize = readSize;
				Log.i(TAG, "audio pcm size="+readSize);
				//设置pcm数据,进行aac编码
				mFFAacEncoderJni.setPcmData(mAudioPCMData.mData, readSize);
			}
		}
		if(PCM_DUMP_DEBUG && null != outPCM){
			try {
				outPCM.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		//中止录音、释放
		mAudioRecord.stop();
		mAudioRecord.release();
		//中止音频编码
		mFFAacEncoderJni.stop();
		Log.i(TAG,"AudioRecordGet thread exit success");
	}		
}

                                                                                                                                                                                     

2 PCM编码成AAC

1 初始化编码器ide

//初始化ffmpeg 编码器
mFFAacEncoderJni.start();

FFAacEncoder代码以下
post

public class FFAacEncoder {
	private String TAG = "FFAacEncoder java";
	//load .so
	static{
		System.loadLibrary("avcodec-57");
		System.loadLibrary("avdevice-57");
		System.loadLibrary("avfilter-6");
		System.loadLibrary("avformat-57");
		System.loadLibrary("avutil-55");
		System.loadLibrary("postproc-54");
		System.loadLibrary("swresample-2");
		System.loadLibrary("swscale-4");
		System.loadLibrary("aacEncoder");
	}
	
	private int mNativeContext = 0;
	//初始化编码器
	private native final void nativeStart();
	//对pcm数据进行编码
	private native final void nativeSetPcmData(byte[] pcm, int len);
	//必要的清理
	private native final void nativeStop();
	
	public void start(){
		nativeStart();
	}
	
	public void setPcmData(byte[] pcm, int len){
		nativeSetPcmData(pcm, len);
	}
	
	public void stop(){
		nativeStop();
	}
}
调用nativeStart方法。

最终调的代码以下(初始化):ui

int AacCodec::start(){
	ALOGW("start");
	av_register_all();
	//avcodec_register_all();
	mAVCodec = avcodec_find_encoder(AV_CODEC_ID_AAC);//查找AAC编码器
	if(!mAVCodec){
		ALOGE("encoder AV_CODEC_ID_AAC not found");
		return -1;
	}
	mAVCodecContext = avcodec_alloc_context3(mAVCodec);
	if(mAVCodecContext != NULL){
		mAVCodecContext->codec_id         = AV_CODEC_ID_AAC;
		mAVCodecContext->codec_type       = AVMEDIA_TYPE_AUDIO;
		mAVCodecContext->bit_rate         = 12200;
		mAVCodecContext->sample_fmt       = AV_SAMPLE_FMT_FLTP;
		mAVCodecContext->sample_rate      = 8000;
		mAVCodecContext->channel_layout   = AV_CH_LAYOUT_MONO;
		mAVCodecContext->channels         = av_get_channel_layout_nb_channels(mAVCodecContext->channel_layout);
	}else {
		ALOGE("avcodec_alloc_context3 fail");
		return -1;
	}
	ALOGW("start  3 channels %d",mAVCodecContext->channels);
	if(avcodec_open2(mAVCodecContext, mAVCodec, NULL) < 0){
		ALOGE("aac avcodec open fail");
		av_free(mAVCodecContext);
		mAVCodecContext = NULL;
		return -1;
	}
	mAVFrame = av_frame_alloc();
	if(!mAVFrame) {
		avcodec_close(mAVCodecContext);
		av_free(mAVCodecContext);
		mAVCodecContext = NULL;
		return -1;
	}
	mAVFrame->nb_samples = mAVCodecContext->frame_size;
	mAVFrame->format = mAVCodecContext->sample_fmt;
	mAVFrame->channel_layout = mAVCodecContext->channel_layout;

	mBufferSize = av_samples_get_buffer_size(NULL, mAVCodecContext->channels, mAVCodecContext->frame_size, mAVCodecContext->sample_fmt, 0);
	if(mBufferSize < 0){
		ALOGE("av_samples_get_buffer_size fail");
		av_frame_free(&mAVFrame);
		mAVFrame = NULL;
		avcodec_close(mAVCodecContext);
		av_free(mAVCodecContext);
		mAVCodecContext = NULL;
		return -1;
	}
	mEncoderData = (uint8_t *)av_malloc(mBufferSize);

	if(!mEncoderData){
		ALOGE("av_malloc fail");
		av_frame_free(&mAVFrame);
		mAVFrame = NULL;
		avcodec_close(mAVCodecContext);
		av_free(mAVCodecContext);
		mAVCodecContext = NULL;
		return -1;
	}

	avcodec_fill_audio_frame(mAVFrame, mAVCodecContext->channels, mAVCodecContext->sample_fmt, (const uint8_t*)mEncoderData, mBufferSize, 0);

	if(DUMP_DEBUG){//存AAC原始音频数据
		mAVFormatContext = avformat_alloc_context();
		mAVOUtputFormat = av_guess_format(NULL, outFile, NULL);
		mAVFormatContext->oformat = mAVOUtputFormat;

		//Open output URL
		if (avio_open(&mAVFormatContext->pb, outFile, AVIO_FLAG_READ_WRITE) < 0){
			printf("Failed to open output file!\n");
			return -1;
		}
		mAVStream = avformat_new_stream(mAVFormatContext, 0);
		if (!mAVStream){
			return -1;
		}
		av_dump_format(mAVFormatContext, 0, outFile, 1);
		//Write Header
		avformat_write_header(mAVFormatContext, NULL);
		}
	return 0;
}

2 音频编码
//设置pcm数据,进行aac编码
mFFAacEncoderJni.setPcmData(mAudioPCMData.mData, readSize);
调用nativeSetPcmData

C++层代码,经过编码获取的AAC原始数据不一样播放(存储在/sdcard/test.aac文件中,不能播放),编码

须要添加adts header(不懂的能够了解一下AAC格式),这样才能够正常播放。/sdcard/adts.aac该文件添加了header,能够正常播放。
spa

int AacCodec::encode_pcm_data(void* pIn, int frameSize){
	int encode_ret = -1;
	int got_packet_ptr = 0;
	AVPacket pkt;
	av_init_packet(&pkt);
	pkt.data = NULL;
	pkt.size = 0;

	if(mAVCodecContext && mAVFrame){
		short2float((int16_t *)pIn, mEncoderData, frameSize/2);
		mAVFrame->data[0] = mEncoderData;
		mAVFrame->pts = 0;
		//音频编码
		encode_ret = avcodec_encode_audio2(mAVCodecContext, &pkt, mAVFrame, &got_packet_ptr);
		ALOGW("encode_pcm_data ----encode_ret:%d,got_packet_ptr:%d",encode_ret,got_packet_ptr);
		if(encode_ret < 0){
			ALOGE("Failed to encode!\n");
			return encode_ret;
		}
		ALOGW("encode_pcm_data ----size =%d",pkt.size);
		pkt.stream_index = mAVStream->index;
		void *adts;
		if(DUMP_DEBUG && pkt.size > 0){
			if(mADTSFile){
				void *adts = malloc(ADTS_HEADER_LENGTH);
                                //添加adts header 能够正常播放。
                               addADTSheader((uint8_t *)adts, pkt.size+ADTS_HEADER_LENGTH);
				fwrite(adts, 1, ADTS_HEADER_LENGTH, mADTSFile);
				fwrite(pkt.data, 1, pkt.size, mADTSFile);
				free(adts);
			}
			//无adts header,aac原始数据,不能播放
			mAVOUtputFormat->write_packet(mAVFormatContext, &pkt);
		}
		av_free_packet(&pkt);
	}
	return encode_ret;
}

添加adts header 代码。这里要设置采样率下标(不一样采样率对应不一样下标,这个能够查一下),声道数。线程

void AacCodec::addADTSheader(uint8_t * in, int packet_size){
	int sampling_frequency_index = 11; //采样率下标,11表示8000
	int channel_configuration = mAVCodecContext->channels; //声道数
	in[0] = 0xFF;
	in[1] = 0xF9;
	in[2] = 0x40 | (sampling_frequency_index << 2) | (channel_configuration >> 2);//0x6c;
	in[3] = (channel_configuration & 0x3) << 6;
	in[3] |= (packet_size & 0x1800) >> 11;
	in[4] = (packet_size & 0x1FF8) >> 3;
	in[5] = ((((unsigned char)packet_size) & 0x07) << 5) | (0xff >> 3);
	in[6] = 0xFC;
}

                                                                                                                                                                                                                                             

3 注意

我这里录音
设计

采样率为8000HZ、声道为单声道,mBufferSizeInBytes 使用AudioRecord.getMinBufferSize(mSampleRateHz, mChannelConfig, mAudioFormat); 编码时会失败,使用2048 和4096都没有问题。code


若是采样率要用16000HZ、声道为双声道,则要修改的地方有

private int mSampleRateHz = 16000;
//MONO为单声道 ,STEREO为双声道
private int mChannelConfig = AudioFormat.CHANNEL_IN_STEREO;
private int mBufferSizeInBytes = 4096;

mAVCodecContext->sample_rate      = 16000;
mAVCodecContext->channel_layout   = AV_CH_LAYOUT_STEREO;

int sampling_frequency_index = 8;



demo下载地址:http://www.demodashi.com/demo/10512.html