1. 程式人生 > >(十四) x264視訊編碼、faac音訊編碼、rtmpdump推流

(十四) x264視訊編碼、faac音訊編碼、rtmpdump推流

#include "hjcommon.hpp"
#include "x264.h"
#include "rtmp.h"
#include "faac.h"
extern "C" {
	#include "queue.h"
}

// video
static x264_picture_t pic_in; // x264編碼輸入的影象
static x264_picture_t pic_out; // x264編碼輸出的影象
static int y_len, u_len, v_len; // Y U V 個數
static x264_t * video_encode_handle; // x264編碼器
//執行緒處理
static pthread_mutex_t mutex;
static pthread_cond_t cond;
static bool is_pushing = FALSE; // 是否直播
static unsigned int start_time; //
static char *rtmp_path; // rtmp流媒體地址

// audio
static faacEncHandle audio_encode_handle; // faac音訊編碼處理器
static unsigned long nInputSamples; // 輸入的取樣個數
static unsigned long nMaxOutputBytes; // 編碼輸出之後的位元組數

// 全域性引用
static jobject jobj_push_native;
static jclass jcls_push_native;
static jmethodID jmid_throw_native_error;
static const int CONNECT_FAILED = 101;
static const int INIT_FAILED = 102;

/**
 * 向Java層傳送錯誤資訊
 */
static void throwNativeError(JNIEnv *env,int code){
	env->CallVoidMethod(jobj_push_native, jmid_throw_native_error, code);
}

/**
 * 加入RTMPPacket佇列,等待發送執行緒傳送
 */
static void add_rtmp_packet(RTMPPacket *packet){
	pthread_mutex_lock(&mutex);
	if(is_pushing){
		queue_append_last(packet);
	}
	pthread_cond_signal(&cond); // 修改執行緒條件,讓pthread_cond_wait之後的程式碼繼續執行,同一個pthread_cond_t可以重複的 signal 與 wait 操作
	pthread_mutex_unlock(&mutex);
}

/**
 * 新增AAC頭資訊
 */
static void add_aac_sequence_header(){
	unsigned char *buf = 0; // 獲取到的aac的頭資訊
	unsigned long len; //長度
	/*
	 int FAACAPI faacEncGetDecoderSpecificInfo( // 獲取aac頭資訊
		 faacEncHandle hEncoder, // faac 編碼器
		 unsigned char **ppBuffer, // 獲取到的aac的頭資訊
		 unsigned long *pSizeOfDecoderSpecificInfo // 獲取到的aac的頭資訊的長度
	 );
	 */
	faacEncGetDecoderSpecificInfo(audio_encode_handle, &buf, &len);
	LOGD("faacEncGetDecoderSpecificInfo aac的頭長度=%lu", len); // faacEncGetDecoderSpecificInfo aac的頭長度=2
	int body_size = 2 + len;
	RTMPPacket *packet = (RTMPPacket *) malloc(sizeof(RTMPPacket));
	//RTMPPacket初始化
	RTMPPacket_Alloc(packet,body_size);
	RTMPPacket_Reset(packet);

	unsigned char * body = (unsigned char *) packet->m_body;
	//頭資訊配置
	/*AF 00 + AAC RAW data*/
	body[0] = 0xAF;//10 5 SoundFormat(4bits):10=AAC 5 = Nellymoser 8 kHz mono,SoundRate(2bits):3=44kHz,SoundSize(1bit):1=16-bit samples,SoundType(1bit):1=Stereo sound
	body[1] = 0x00;//AACPacketType:0表示AAC sequence header
	memcpy(&body[2], buf, len); /*spec_buf是AAC sequence header資料*/
	packet->m_packetType = RTMP_PACKET_TYPE_AUDIO;
	packet->m_nBodySize = body_size;
	packet->m_nChannel = 0x04;
	packet->m_hasAbsTimestamp = 0;
	packet->m_nTimeStamp = 0;
	packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM;
	add_rtmp_packet(packet);
	free(buf);
}

/**
 * 新增AAC rtmp packet
 */
static void add_aac_body(unsigned char *buf, int len){
	int body_size = 2 + len; // 多配置的2個位元組,為 AAC header 的長度
	RTMPPacket *packet = (RTMPPacket *) malloc(sizeof(RTMPPacket));
	//RTMPPacket初始化
	RTMPPacket_Alloc(packet, body_size);
	RTMPPacket_Reset(packet);

	unsigned char * body = (unsigned char *) packet->m_body;
	//頭資訊配置
	/*AF 00 + AAC RAW data*/
	body[0] = 0xAF;//10 5 SoundFormat(4bits):10=AAC 5 = Nellymoser 8 kHz mono,SoundRate(2bits):3=44kHz,SoundSize(1bit):1=16-bit samples,SoundType(1bit):1=Stereo sound
	body[1] = 0x01;//AACPacketType:1表示AAC raw ,即 AAC 音訊資料
	memcpy(&body[2], buf, len); /*spec_buf是AAC raw資料*/
	packet->m_packetType = RTMP_PACKET_TYPE_AUDIO;
	packet->m_nBodySize = body_size;
	packet->m_nChannel = 0x04;
	packet->m_hasAbsTimestamp = 0;
	packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
	packet->m_nTimeStamp = RTMP_GetTime() - start_time;
	add_rtmp_packet(packet);
}

/**
 * 從佇列中不斷拉取RTMPPacket傳送給流媒體伺服器)
 */
static void *push_thread(void * arg){
	JNIEnv* env = 0;//獲取當前執行緒JNIEnv
	hj_javavm->AttachCurrentThread(&env, NULL);

	//建立RTMP連線
	RTMP *rtmp = RTMP_Alloc(); // 建立 RTMP
	if(!rtmp){
		LOGE("rtmp初始化失敗");
		goto end;
	}
	RTMP_Init(rtmp); // 初始化 RTMP
	rtmp->Link.timeout = 5; //連線超時的時間,單位秒
	//設定流媒體地址
	RTMP_SetupURL(rtmp, rtmp_path);
	//釋出rtmp資料流
	RTMP_EnableWrite(rtmp);
	//建立連線,建立連線的時候可以發一個包過去,這裡NULL了
	if(!RTMP_Connect(rtmp, NULL)){
		LOGE("%s", "RTMP 連線失敗");
		throwNativeError(env, CONNECT_FAILED);
		goto end;
	}
	//計時
	start_time = RTMP_GetTime();
	if(!RTMP_ConnectStream(rtmp, 0)){ //連線流
		LOGE("%s", "RTMP ConnectStream failed");
		throwNativeError(env, CONNECT_FAILED);
		goto end;
	}
	is_pushing = TRUE;
	//傳送AAC頭資訊,只需要傳送一次
	add_aac_sequence_header();

	while(is_pushing){
		//傳送
		pthread_mutex_lock(&mutex);
		pthread_cond_wait(&cond,&mutex); // pthread_cond_wait(阻塞等待pthread_cond_t被喚醒,會unlock互斥,被喚醒後會重新lock互斥,while迴圈)
		//取出佇列中的RTMPPacket
		RTMPPacket *packet = (RTMPPacket *) queue_get_first();
		if(packet){
			queue_delete_first(); //移除
			packet->m_nInfoField2 = rtmp->m_stream_id; //RTMP協議,stream_id資料
			int i = RTMP_SendPacket(rtmp, packet, TRUE); //TRUE放入librtmp佇列中,並不是立即傳送
			if(!i){
				LOGE("RTMP 斷開");
				RTMPPacket_Free(packet);
				pthread_mutex_unlock(&mutex);
				goto end;
			}else{
				LOGV("%s", "rtmp send packet");
			}
			RTMPPacket_Free(packet);
		}

		pthread_mutex_unlock(&mutex);
	}
end:
	LOGI("%s","釋放資源");
	free(rtmp_path);
	RTMP_Close(rtmp); // 關閉 RTMP
	RTMP_Free(rtmp); // 釋放 RTMP

	hj_javavm->DetachCurrentThread();
	return 0;
}

/**
 * 傳送h264 SPS與PPS引數集
 * rtmpdump 使用流程,參看 librtmp/librtmp.3.html
 */
static void add_264_sequence_header(unsigned char* pps, unsigned char* sps, int pps_len, int sps_len)
{
	int body_size = 16 + sps_len + pps_len; //按照H264標準配置SPS和PPS,共使用了16位元組
	RTMPPacket *packet = (RTMPPacket *) malloc(sizeof(RTMPPacket)); // 為 RTMPPacket 分配記憶體,RTMPPacket 對應 rtmp 協議中的 message 模組
	//RTMPPacket初始化,為 RTMPPacket 的資料初始化記憶體
	RTMPPacket_Alloc(packet, body_size); // 這裡,RTMPPacket body包含:h264配置、SPS與PPS資料、RTMP協議資料
	RTMPPacket_Reset(packet);

	// 設定 RTMPPacket 資料
	// 一、h264配置
	unsigned char * body = (unsigned char *) packet->m_body; // packet->m_body 表示body資料
	int i = 0; // 從第0個位元組開始,總共16位元組(非連續)
	//二進位制表示:00010111
	body[i++] = 0x17;//VideoHeaderTag:FrameType(1=key frame)+CodecID(7=AVC)
	// 如果視訊的格式是AVC(H.264)的話,VideoTagHeader會多出4個位元組的資訊,全部置為0
	body[i++] = 0x00;//AVCPacketType = 0表示設定AVCDecoderConfigurationRecord
	//composition time 0x000000 24bit ?
	body[i++] = 0x00;
	body[i++] = 0x00;
	body[i++] = 0x00;

	/*AVCDecoderConfigurationRecord*/
	body[i++] = 0x01;//configurationVersion,版本為1 隨便給
	body[i++] = sps[1];//AVCProfileIndication
	body[i++] = sps[2];//profile_compatibility
	body[i++] = sps[3];//AVCLevelIndication
	//?
	body[i++] = 0xFF;//lengthSizeMinusOne,H264 視訊中 NALU的長度,計算方法是 1 + (lengthSizeMinusOne & 3),實際測試時發現總為FF,計算結果為4.

	// 二、SPS與PPS資料
	/*sps*/
	body[i++] = 0xE1;//numOfSequenceParameterSets:SPS的個數,計算方法是 numOfSequenceParameterSets & 0x1F,實際測試時發現總為E1,計算結果為1.
	body[i++] = (sps_len >> 8) & 0xff;//sequenceParameterSetLength:SPS的長度
	body[i++] = sps_len & 0xff;//sequenceParameterSetNALUnits
	memcpy(&body[i], sps, sps_len); // 設定 SPS 資料
	i += sps_len;

	/*pps*/
	body[i++] = 0x01;//numOfPictureParameterSets:PPS 的個數,計算方法是 numOfPictureParameterSets & 0x1F,實際測試時發現總為E1,計算結果為1.
	body[i++] = (pps_len >> 8) & 0xff;//pictureParameterSetLength:PPS的長度
	body[i++] = (pps_len) & 0xff;//PPS
	memcpy(&body[i], pps, pps_len); // 設定 PPS 資料
	i += pps_len;

	// 三、RTMP協議資料,rtmpdump會自動構建好 message 的header與body資料
	//Message Type,RTMP_PACKET_TYPE_VIDEO:0x09    0x04表示Ping包,0x08為audio,0x09為video
	packet->m_packetType = RTMP_PACKET_TYPE_VIDEO; // packet->m_packetType 表示rtmp包的型別,視訊
	//Payload Length
	packet->m_nBodySize = body_size; // 重新設定 mesage body 的長度
	//Time Stamp:4位元組
	//記錄了每一個tag相對於第一個tag(File Header)的相對時間。
	//以毫秒為單位。而File Header的time stamp永遠為0。
	packet->m_nTimeStamp = 0; // 時間戳
	packet->m_hasAbsTimestamp = 0; // timestamp absolute or relative?  0表示relative?
	packet->m_nChannel = 0x04; //Channel ID,Audio和Vidio通道
	packet->m_headerType = RTMP_PACKET_SIZE_MEDIUM; // HeaderType (fmt):決定了Chunk Message Header的編碼方式和大小,在第一個位元組的高兩位
	//將RTMPPacket加入佇列
	add_rtmp_packet(packet);
}

/**
 * 傳送h264幀資訊
 */
static void add_264_body(unsigned char *buf, int len) // buf 為 nalu資料
{
	//去掉起始碼(界定符)  如果NALU對應的Slice為一幀的開始,則用4位元組表示,即0x00000001;否則用3位元組表示,0x000001。
	if (buf[2] == 0x00) // 00 00 00 01
	{
		buf += 4;
		len -= 4;
	}
	else if (buf[2] == 0x01) // 00 00 01
	{
		buf += 3;
		len -= 3;
	}
	int body_size = len + 9; // + 9 是因為在 RTMPPacket body 多配置9個位元組資訊
	RTMPPacket *packet = (RTMPPacket *) malloc(sizeof(RTMPPacket));
	RTMPPacket_Alloc(packet, body_size);

	// 新增 H264 配置
	unsigned char * body = (unsigned char *) packet->m_body;
	//當NAL頭資訊中,type(5位)等於5,說明這是關鍵幀NAL單元
	//buf[0] NAL Header與運算,獲取type,根據type判斷關鍵幀和普通幀
	//00000101 & 00011111(0x1f) = 00000101
	int type = buf[0] & 0x1f;
	//Inter Frame 幀間壓縮
	body[0] = 0x27;//VideoHeaderTag:FrameType(2=Inter Frame)+CodecID(7=AVC)
	//IDR I幀影象
	if (type == NAL_SLICE_IDR) {
		body[0] = 0x17;//VideoHeaderTag:FrameType(1=key frame)+CodecID(7=AVC)
	}
	//AVCPacketType = 1
	body[1] = 0x01; /*nal unit,NALUs(AVCPacketType == 1)*/
	body[2] = 0x00; //composition time 0x000000 24bit
	body[3] = 0x00;
	body[4] = 0x00;

	//寫入NALU資訊,右移8位,一個位元組的讀取?
	body[5] = (len >> 24) & 0xff;
	body[6] = (len >> 16) & 0xff;
	body[7] = (len >> 8) & 0xff;
	body[8] = (len) & 0xff;

	// 設定 NALU 資料到 RTMPPacket
	/*copy data*/
	memcpy(&body[9], buf, len);

	// RTMP協議資料,rtmpdump會自動構建好 message 的header與body資料
	packet->m_hasAbsTimestamp = 0;
	packet->m_nBodySize = body_size;
	packet->m_packetType = RTMP_PACKET_TYPE_VIDEO;//當前packet的型別:Video
	packet->m_nChannel = 0x04;
	packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
	//packet->m_nTimeStamp = -1;
	packet->m_nTimeStamp = RTMP_GetTime() - start_time;//記錄了每一個tag相對於第一個tag(File Header)的相對時間,File Header的time stamp永遠為0
	add_rtmp_packet(packet);
}

JNIEXPORT void JNICALL Java_hankin_hjmedia_ff_pusher_PushNative_startPush(JNIEnv *env, jobject instance, jstring url_) // 開始推流,推流地址
{
	//jobj(PushNative物件)
	jobj_push_native = env->NewGlobalRef(instance);
	jclass jcls_push_native_tmp = env->GetObjectClass(instance);
	jcls_push_native = (jclass) env->NewGlobalRef(jcls_push_native_tmp);
	//PushNative.throwNativeError
	jmid_throw_native_error = env->GetMethodID(jcls_push_native_tmp, "throwNativeError", "(I)V");

	//初始化的操作
	char url_cstr[256];
	hjcpyJstr2char(env, url_, url_cstr);
	//複製url_cstr內容到rtmp_path
	rtmp_path = (char *) malloc(strlen(url_cstr) + 1);
	memset(rtmp_path, 0, strlen(url_cstr) + 1);
	memcpy(rtmp_path, url_cstr, strlen(url_cstr));

	//初始化互斥鎖與條件變數
	pthread_mutex_init(&mutex, NULL);
	pthread_cond_init(&cond, NULL);

	//建立佇列
	create_queue();
	//啟動消費者執行緒(從佇列中不斷拉取RTMPPacket傳送給流媒體伺服器)
	pthread_t push_thread_id;
	pthread_create(&push_thread_id, NULL, push_thread, NULL);
}

JNIEXPORT void JNICALL Java_hankin_hjmedia_ff_pusher_PushNative_stopPush(JNIEnv *env, jobject instance) // 停止推流
{
	is_pushing = FALSE;
}

JNIEXPORT void JNICALL Java_hankin_hjmedia_ff_pusher_PushNative_release(JNIEnv *env, jobject instance) // 釋放
{
	env->DeleteGlobalRef(jcls_push_native);
	env->DeleteGlobalRef(jobj_push_native);
}

JNIEXPORT void JNICALL Java_hankin_hjmedia_ff_pusher_PushNative_setVideoOptions(JNIEnv *env, jobject instance,
							jint width, jint height, jint bitrate, jint fps) // 設定視訊引數,寬、高、位元速率、幀率
{
	y_len = width * height;
	u_len = y_len / 4;
	v_len = u_len;

	// 參看 x264 的 example.c 原始碼
	// 1、初始化x264編碼引數
	x264_param_t param;
	/*
	 int x264_param_default_preset( // 設定x264編碼引數
		 x264_param_t *param,
		 const char *preset, // 可以控制編碼速度,傳值參看原始碼
		 const char *tune
	 )
	 */
	int ret = x264_param_default_preset(&param, "ultrafast", "zerolatency"); // ultrafast 表示編碼超級快,zerolatency 表示沒有B幀
	if (ret!=0) LOGE("x264_param_default_preset error.");

	// 2、設定param、level與profile
	param.i_bitdepth = 8;
	param.i_csp = X264_CSP_I420; // 設定編碼時,輸入的影象的畫素格式,I420
	param.i_width  = width; // 輸入的影象的寬
	param.i_height = height; // 高
	param.b_vfr_input = 0; // 位元速率控制,傳1表示用 timebase and timestamps 控制(需要給每幀設定timebase and timestamps),傳0表示用fps進行位元速率控制(自動)
	param.b_repeat_headers = 1; // put SPS/PPS before each keyframe , 是否將 SPS/PPS 放入每一個關鍵幀,放入SPS/PPS是為了提高影象的糾錯能力
	param.i_level_idc = 51; // 51 表示level設定為 5.1
	//引數i_rc_method表示位元速率控制策略,CQP(恆定質量),CRF(恆定位元速率),ABR(平均位元速率)
	param.rc.i_rc_method = X264_RC_CRF; // 恆定位元速率,會盡量控制在固定位元速率
	param.rc.i_bitrate = bitrate / 1000; // 位元速率(位元率,單位Kbps)
	param.rc.i_vbv_max_bitrate = bitrate / 1000 * 1.2; // 瞬時最大位元速率
	param.i_fps_num = fps; // 幀率分子
	param.i_fps_den = 1; // 幀率分母
	param.i_timebase_den = param.i_fps_num; // 這裡 timebase 設定為fps值,是因為 param.b_vfr_input = 0 ,位元速率用fps控制,因為不設定B幀,所以PTS與DTS是相同的
	param.i_timebase_num = param.i_fps_den;
	param.i_threads = 1; // 並行編碼執行緒數量,這裡只給1個,0預設為多執行緒
	/*
	 int x264_param_apply_profile( // 設定檔次,level與profile都需要設定,Profile與Level(級別)是用來約束解析度、幀率和位元速率的。具體參照h264圖表
		 x264_param_t *param, // param
		 const char *profile // 檔次,profile
	 )
	 */
	ret = x264_param_apply_profile(&param, "baseline"); // baseline 表示編碼只有I、P幀,沒有B幀
	if (ret!=0) LOGE("x264_param_apply_profile error.");

	// 3、 輸入影象初始化
	ret = x264_picture_alloc(&pic_in, param.i_csp, param.i_width, param.i_height);
	if (ret!=0) LOGE("x264_picture_alloc error.");
	pic_in.i_pts=0; // pts 初始為0

	// 4、開啟編碼器
	video_encode_handle = x264_encoder_open(&param);
	if (!video_encode_handle) LOGE("x264_encoder_open error.");
	LOGI("%s","x264_encoder_open 成功");
}

JNIEXPORT void JNICALL Java_hankin_hjmedia_ff_pusher_PushNative_setAudioOptions(JNIEnv *env, jobject instance,
							jint sampleRateInHz, jint channel) // 設定音訊引數,取樣率、聲道數
{
	/*
		faacEncHandle FAACAPI faacEncOpen( // 開啟faac音訊編碼器
			unsigned long sampleRate, // 取樣率
			unsigned int numChannels, // 聲道數
			unsigned long *inputSamples, // 輸入的取樣個數
			unsigned long *maxOutputBytes // 編碼輸出之後的位元組數
		);
	*/
	audio_encode_handle = faacEncOpen(sampleRateInHz, channel, &nInputSamples, &nMaxOutputBytes);
	if(!audio_encode_handle) LOGE("音訊編碼器開啟失敗");
	LOGD("輸入的取樣個數=%lu, 編碼輸出之後的位元組數=%lu", nInputSamples, nMaxOutputBytes); // 輸入的取樣個數=2048, 編碼輸出之後的位元組數=1536

	faacEncConfigurationPtr p_config = faacEncGetCurrentConfiguration(audio_encode_handle); // 獲取 faacEncConfiguration
	p_config->mpegVersion = MPEG4; // MPEG 版本,2 或 4
	p_config->allowMidside = 1;
	p_config->aacObjectType = LOW; // 這裡給的是低的配置
	p_config->outputFormat = 0; //輸出是否包含ADTS頭 (0 = Raw; 1 = ADTS)
	p_config->useTns = 1; //時域噪音控制,大概就是消爆音
	p_config->useLfe = 0;
	//p_config->inputFormat = FAAC_INPUT_16BIT; // 輸入的音訊格式,預設 FAAC_INPUT_32BIT
	p_config->quantqual = 100;
	p_config->bandWidth = 0; //頻寬
	p_config->shortctl = SHORTCTL_NORMAL;

	if(!faacEncSetConfiguration(audio_encode_handle, p_config)) // 設定音訊編碼引數,返回0表示失敗
	{
		LOGE("%s","音訊編碼器配置失敗..");
		throwNativeError(env, INIT_FAILED);
		return;
	}
	LOGI("%s","音訊編碼器配置成功");
}

JNIEXPORT void JNICALL Java_hankin_hjmedia_ff_pusher_PushNative_sendVideo(JNIEnv *env, jobject instance, jbyteArray data_) // 傳送視訊資料
{
	// android攝像頭預設PreviewFormat是ImageFormat.NV21,這裡轉成x264中設定的 I420
	jbyte * nv21Buffer = env->GetByteArrayElements(data_, 0);
	uint8_t * u = pic_in.img.plane[1]; // pic_in.img.plane 下標0表示y的指標,1表示u的指標,2表示v的指標,(因為pic_in設定的是I420)
	uint8_t * v = pic_in.img.plane[2];
	memcpy(pic_in.img.plane[0], nv21Buffer, y_len); // 拷貝 y
	for (int i = 0; i < v_len; i++)
	{
		*(u + i) = *(nv21Buffer + y_len + i * 2 + 1); // u v 轉換
		*(v + i) = *(nv21Buffer + y_len + i * 2);
	}

	// 5、編碼
	x264_nal_t * nal = 0; // x264編碼得到的NALU陣列,(x264編碼之後的資料由一個一個NALU組成)
	int n_nal = -1; // 編碼得到的NALU的個數
	/*
		 int x264_encoder_encode( // 編碼,returns the number of bytes in the returned NALs ,返回值小於0,表示失敗
			 x264_t *h, // 編碼器
			 x264_nal_t **pp_nal, // nal指標陣列
			 int *pi_nal, // nal的個數
			 x264_picture_t *pic_in, // 輸入的影象
			 x264_picture_t *pic_out // 編碼之後的影象
		 )
	 */
	int ret = x264_encoder_encode(video_encode_handle, &nal, &n_nal, &pic_in, &pic_out);
	if (ret<0) LOGE("x264_encoder_encode error.");
	LOGV("n_nal=%d", n_nal); // n_nal=4   someone

	// 6、使用rtmp協議將資料傳送到nginx流媒體伺服器
	int sps_len = -1, pps_len = -1;
	unsigned char sps[100] = {0}; // 長度給 100 是隨便給的?
	unsigned char pps[100];
	memset(sps, 0, 100);
	memset(pps, 0, 100);
	pic_in.i_pts += 1; // pts 順序累加
	for (int i=0; i < n_nal; i++) //遍歷NALU陣列,根據NALU的型別判斷
	{
		if (nal[i].i_type == NAL_SPS) // nal[i].i_type 對應NALU頭中的type
		{
			//複製SPS資料
			sps_len = nal[i].i_payload - 4; // nal[i].i_payload 表示NALU所有資料指標的長度
			memcpy(sps, nal[i].p_payload + 4, sps_len); // 不復制四位元組起始碼,nal[i].p_payload 表示NALU所有資料指標
		}
		else if (nal[i].i_type == NAL_PPS) // PPS
		{
			//複製PPS資料
			pps_len = nal[i].i_payload - 4;
			memcpy(pps, nal[i].p_payload + 4, pps_len); //不復制四位元組起始碼
			//傳送序列資訊(頭),編碼後的NALU包含了SPS 或 PPS 的就是關鍵幀?
			add_264_sequence_header(pps, sps, pps_len, sps_len); // 將 SPS PPS 設到關鍵幀中,以提高視訊的糾錯率
		}
		else // 普通幀
		{
			//傳送幀資訊(媒體資料)
			add_264_body(nal[i].p_payload, nal[i].i_payload);
		}
		/* someone
			i=0, i_type=7, i_payload=25, sps_len=21, pps_len=-1
			i=1, i_type=8, i_payload=8, sps_len=21, pps_len=4
			i=2, i_type=6, i_payload=585, sps_len=21, pps_len=4
			i=3, i_type=5, i_payload=10082, sps_len=21, pps_len=4
		 */
		LOGV("i=%d, i_type=%d, i_payload=%d, sps_len=%d, pps_len=%d", i, nal[i].i_type, nal[i].i_payload, sps_len, pps_len);
	}

	env->ReleaseByteArrayElements(data_, nv21Buffer, 0); // 釋放java的byte陣列引用
}

JNIEXPORT void JNICALL Java_hankin_hjmedia_ff_pusher_PushNative_sendAudio(JNIEnv *env, jobject instance, jbyteArray data_, jint len) // 傳送音訊資料
{
	int *pcmbuf;
	unsigned char *bitbuf;
	jbyte* b_buffer = env->GetByteArrayElements(data_, 0);
	pcmbuf = (int*) malloc(nInputSamples * sizeof(int));
	bitbuf = (unsigned char*) malloc(nMaxOutputBytes * sizeof(unsigned char));
	int nByteCount = 0;
	unsigned int nBufferSize = (unsigned int) len / 2; // 因為聲道數設定的2,所以 / 2 ? no no
	unsigned short* buf = (unsigned short*) b_buffer;
	while (nByteCount < nBufferSize) {
		int audioLength = nInputSamples;
		if ((nByteCount + nInputSamples) >= nBufferSize) {
			audioLength = nBufferSize - nByteCount;
		}
		int i;
		for (i = 0; i < audioLength; i++) {//每次從實時的pcm音訊佇列中讀出量化位數為8的pcm資料。
			int s = ((int16_t *) buf + nByteCount)[i];
			pcmbuf[i] = s << 8;//用8個二進位制位來表示一個取樣量化點(模數轉換)
		}
		nByteCount += nInputSamples;
		/*
		 int FAACAPI faacEncEncode( // 利用FAAC進行編碼
			 faacEncHandle hEncoder, // faacEncHandle 編碼器
			 int32_t * inputBuffer, // 為轉換後的pcm流資料
			 unsigned int samplesInput, // 為呼叫faacEncOpen時得到的輸入取樣數
			 unsigned char *outputBuffer, // 為編碼後的資料buff
			 unsigned int bufferSize // 為呼叫faacEncOpen時得到的最大輸出位元組數
		 );
		 */
		int byteslen = faacEncEncode(audio_encode_handle, pcmbuf, audioLength, bitbuf, nMaxOutputBytes);
		if (byteslen < 1) {
			continue;
		}
		add_aac_body(bitbuf, byteslen);//從bitbuf中得到編碼後的aac資料流,放到資料佇列
	}
	env->ReleaseByteArrayElements(data_, b_buffer, NULL);
	if (bitbuf)
		free(bitbuf);
	if (pcmbuf)
		free(pcmbuf);

	// 第二種方式,不可行?
	//unsigned char *bitbuf = (unsigned char *) malloc(
	//        nMaxOutputBytes * sizeof(unsigned char));
	//jbyte *b_buffer = env->GetByteArrayElements(data_, 0);
	//int byteslen = faacEncEncode(audio_encode_handle, (int32_t *) b_buffer,
	//                             nInputSamples, bitbuf, nMaxOutputBytes);
	//if (byteslen > 0) {
	//    add_aac_body(bitbuf, byteslen);
	//}
	//env->ReleaseByteArrayElements(data_, b_buffer, 0);
	//if (bitbuf)
	//    free(bitbuf);
}