社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  NGINX

Nginx-RTMP推流(audio)

江郎才无尽 • 6 年前 • 768 次点击  
阅读 85

Nginx-RTMP推流(audio)

需要文中完整代码的可以前往Github上获取,顺便给个star呗。

AAC编码

​ 推送音频跟推送视频差不多,经过数据采集,编码,然后通过RTMP推流。数据采集通常有两种方式,一种是Java层的AudioRecord,另一种是native层opensl es;采集完后就是编码,相比视频比较简单,编码库这里采用FAAC进行交叉编译,这里讲PCM的声音数据编码成AAC编码数据,什么叫AAC编码数据呢?参照维基百科:

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;
      ....
    }
  ....
}

复制代码

这样怎个推流过程就完成了,在服务器上可以查看音频数据推流成功:


今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/LLsVmMmQUI
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/25817
 
768 次点击