(十四) x264視訊編碼、faac音訊編碼、rtmpdump推流
阿新 • • 發佈:2018-12-15
#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(¶m, "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(¶m, "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(¶m); 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); }