Nginx-RTMP推流(audio)
推送音訊跟推送視訊差不多,經過資料採集,編碼,然後通過RTMP推流。資料採集通常有兩種方式,一種是Java層的AudioRecord,另一種是native層opensl es;採集完後就是編碼,相比視訊比較簡單,編碼庫這裡採用FAAC進行交叉編譯,這裡講PCM的聲音資料編碼成AAC編碼資料,什麼叫AAC編碼資料呢?參照維基百科:
ofollow,noindex">zh.wikipedia.org/wiki/進階音訊編碼
高階音訊編碼(Advanced Audio Coding),出現於1997年,基於MPEG-2的音訊編碼技術,目的是取代MP3格式。2000年,MPEG-4標準出現後,AAC重新集成了其特性,為了區別於傳統的MPEG-2 AAC又稱為MPEG-4 AAC。相對於mp3,AAC格式的音質更佳,檔案更小。
AAC的音訊檔案格式有 ADIF & ADTS

一種是在連續的音訊資料的開始處存有解碼資訊,一種是在每一小段音訊資料頭部存放7個或者9個位元組的頭資訊用於播放器解碼。
FAAC交叉庫編譯
瞭解完aac編碼後,下載FAAC, jaist.dl.sourceforge.net/project/faa… 下載完成後,解壓包,參照 ./configure檔案進行引數配置交叉編譯指令碼:
#!/bin/bash NDK_ROOT=/root/android-ndk-r17-beta2 PREFIX=`pwd`/android/armeabi-v7a #注意 Linux 系統為 linux-x86_64, mac為 darwin-x84_64 TOOLCHAIN=$NDK_ROOT/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64 CROSS_COMPILE=$TOOLCHAIN/bin/arm-linux-androideabi FLAGS="-isysroot $NDK_ROOT/sysroot -isystem $NDK_ROOT/sysroot/usr/include/arm-linux-androideabi -D__ANDROID_API__=17 -g -DANDROID -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -march=armv7-a -mfloat-abi=softfp -mfpu=vfpv3-d16 -mthumb -Wa,--noexecstack -Wformat -Werror=format-security -std=c++11-O0-fPIC" export CC="$CROSS_COMPILE-gcc --sysroot=$NDK_ROOT/platforms/android-17/arch-arm" export CFLAGS="$FLAGS" ./configure \ --prefix=$PREFIX \ --host=arm-linux \ --with-pic \ --enable-shared=no make clean make install 複製程式碼
這裡沒有用Mac下編譯,一直出錯,改到雲伺服器上編譯的。成功後會在 faac 的根目錄下生成指定的資料夾:PREFIX= pwd
/android/armeabi-v7a:

拷貝include下的標頭檔案,lib下的靜態庫到專案工程中去,修改CmakeList檔案,然後進行編碼推流

採集—編碼—推流
RTMP推流需要的是aac的裸資料。所以如果編碼出adts格式的資料,需要去掉7個或者9個位元組的adts頭資訊。類似於推送視訊,第一個包總是包含sps和pps的音訊序列包,推送音訊同樣第一個包是包含了接下來資料的格式的音訊序列包,第一個位元組定義如下:

而第二個位元組為0x00與0x01,分別代表序列包與聲音資料包。

資料採集
public AudioChannel(LivePusher livePusher) { .... //初始化AudioRecord。 引數:1、麥克風 2、取樣率 3、聲道數 4、取樣位 audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC, 44100, channelConfig, AudioFormat.ENCODING_PCM_16BIT, minBufferSize > inputSamples ? minBufferSize : inputSamples); } //執行緒執行資料採集 public void startLive() { isLiving = true; executor.submit(new AudioTeask()); } class AudioTeask implements Runnable { @Override public void run() { //啟動錄音機 audioRecord.startRecording(); byte[] bytes = new byte[inputSamples]; while (isLiving) { int len = audioRecord.read(bytes, 0, bytes.length); if (len > 0) { //送去編碼 mLivePusher.native_pushAudio(bytes); } } //停止錄音機 audioRecord.stop(); } } 複製程式碼
AAC編碼
//開啟編碼器 void AudioChannel::setAudioEncInfo(int samplesInHZ, int channels) { //開啟編碼器 mChannels = channels; //3、一次最大能輸入編碼器的樣本數量 也編碼的資料的個數 (一個樣本是16位 2位元組) //4、最大可能的輸出資料編碼後的最大位元組數 audioCodec = faacEncOpen(samplesInHZ, channels, &inputSamples, &maxOutputBytes); //設定編碼器引數 faacEncConfigurationPtr config = faacEncGetCurrentConfiguration(audioCodec); //指定為 mpeg4 標準 config->mpegVersion = MPEG4; //lc 標準 config->aacObjectType = LOW; //16位 config->inputFormat = FAAC_INPUT_16BIT; // 編碼出原始資料 既不是adts也不是adif config->outputFormat = 0; faacEncSetConfiguration(audioCodec, config); //輸出緩衝區 編碼後的資料 用這個緩衝區來儲存 buffer = new u_char[maxOutputBytes]; } extern "C" JNIEXPORT void JNICALL Java_com_dongnao_pusher_live_LivePusher_native_1pushAudio(JNIEnv *env, jobject instance, jbyteArray data_) { if (!audioChannel || !readyPushing) { return; } jbyte *data = env->GetByteArrayElements(data_, NULL); audioChannel->encodeData(data); env->ReleaseByteArrayElements(data_, data, 0); } //資料編碼 void AudioChannel::encodeData(int8_t *data) { //返回編碼後資料位元組的長度 int bytelen = faacEncEncode(audioCodec, reinterpret_cast<int32_t *>(data),inputSamples, buffer,maxOutputBytes); if (bytelen > 0) { //看錶 int bodySize = 2 + bytelen; RTMPPacket *packet = new RTMPPacket; RTMPPacket_Alloc(packet, bodySize); //雙聲道 packet->m_body[0] = 0xAF; if (mChannels == 1) { packet->m_body[0] = 0xAE; } //編碼出的聲音 都是 0x01 packet->m_body[1] = 0x01; //圖片資料 memcpy(&packet->m_body[2], buffer, bytelen); packet->m_hasAbsTimestamp = 0; packet->m_nBodySize = bodySize; packet->m_packetType = RTMP_PACKET_TYPE_AUDIO; packet->m_nChannel = 0x11; packet->m_headerType = RTMP_PACKET_SIZE_LARGE; audioCallback(packet); } } 複製程式碼
編碼完後呼叫 回撥audioCallback(packet),往佇列中塞入資料,在start中從queue中pop data.
void callback(RTMPPacket *packet) { if (packet) { //設定時間戳 packet->m_nTimeStamp = RTMP_GetTime() - start_time; //這裡往佇列裡 塞資料,在start中 pop取資料然後發出去 packets.push(packet); } } void *start(void *args) { .... while(readyPushing) { packets.pop(packet); if (!readyPushing) { break; } if (!packet) { continue; } // 給rtmp的流id packet->m_nInfoField2 = rtmp->m_stream_id; .... } .... } 複製程式碼
這樣怎個推流過程就完成了,在伺服器上可以檢視音訊資料推流成功:
