Nginx-RTMP推流(audio)

須要文中完整代碼的能夠前往Github上獲取,順便給個star唄。linux

AAC編碼

​ 推送音頻跟推送視頻差很少,通過數據採集,編碼,而後經過RTMP推流。數據採集一般有兩種方式,一種是Java層的AudioRecord,另外一種是native層opensl es;採集完後就是編碼,相比視頻比較簡單,編碼庫這裏採用FAAC進行交叉編譯,這裏講PCM的聲音數據編碼成AAC編碼數據,什麼叫AAC編碼數據呢?參照維基百科:android

zh.wikipedia.org/wiki/進階音訊編碼c++

高級音頻編碼(Advanced Audio Coding),出現於1997年,基於MPEG-2的音頻編碼技術,目的是取代MP3格式。2000年,MPEG-4標準出現後,AAC從新集成了其特性,爲了區別於傳統的MPEG-2 AAC又稱爲MPEG-4 AAC。相對於mp3,AAC格式的音質更佳,文件更小。git

AAC的音頻文件格式有 ADIF & ADTSgithub

​ 一種是在連續的音頻數據的開始處存有解碼信息,一種是在每一小段音頻數據頭部存放7個或者9個字節的頭信息用於播放器解碼。shell

FAAC交叉庫編譯

​ 瞭解完aac編碼後,下載FAAC, jaist.dl.sourceforge.net/project/faa… 下載完成後,解壓包,參照 ./configure文件進行參數配置交叉編譯腳本:bash

#!/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文件,而後進行編碼推流ide

採集—編碼—推流

​ RTMP推流須要的是aac的裸數據。因此若是編碼出adts格式的數據,須要去掉7個或者9個字節的adts頭信息。相似於推送視頻,第一個包老是包含sps和pps的音頻序列包,推送音頻一樣第一個包是包含了接下來數據的格式的音頻序列包,第一個字節定義以下:ui

而第二個字節爲0x00與0x01,分別表明序列包與聲音數據包。

數據採集

public AudioChannel(LivePusher livePusher) {
       ....
  //初始化AudioRecord。 參數:一、麥克風 二、採樣率 三、聲道數 四、採樣位
 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;
    //三、一次最大能輸入編碼器的樣本數量 也編碼的數據的個數 (一個樣本是16位 2字節)
    //四、最大可能的輸出數據 編碼後的最大字節數
    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;
      ....
    }
  ....
}

複製代碼

這樣怎個推流過程就完成了,在服務器上能夠查看音頻數據推流成功:

相關文章
相關標籤/搜索