博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Nginx-RTMP推流(audio)
阅读量:6441 次
发布时间:2019-06-23

本文共 4986 字,大约阅读时间需要 16 分钟。

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

AAC编码

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

高级音频编码(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, 下载完成后,解压包,参照 ./configure文件进行参数配置交叉编译脚本:

#!/bin/bashNDK_ROOT=/root/android-ndk-r17-beta2PREFIX=`pwd`/android/armeabi-v7a#注意 Linux 系统为 linux-x86_64, mac为 darwin-x84_64TOOLCHAIN=$NDK_ROOT/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64CROSS_COMPILE=$TOOLCHAIN/bin/arm-linux-androideabiFLAGS="-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=nomake cleanmake 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 JNICALLJava_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
(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://vycwo.baihongyu.com/

你可能感兴趣的文章
ICS g-sensor,light sensor移植记录
查看>>
入职培训笔记记录--day7(1、指针数组与数组指针 2、函数)
查看>>
golang zip 压缩,解压(含目录文件)
查看>>
二分计算x的n次方
查看>>
0505.Net基础班第八天(飞行棋)
查看>>
安卓高手之路之 WindowManager
查看>>
vim+cscope+ctags一些使用笔记
查看>>
LeetCode-Move Zeroes
查看>>
结对第2次作业——WordCount进阶需求
查看>>
Python面向对象之面向对象基本概念
查看>>
PDB文件:每个开发人员都必须知道的
查看>>
Alpha版本测试报告
查看>>
settings.xml配置详解
查看>>
MySQL复制
查看>>
脸上有酒窝,脖子后有痣,胸前有颗痣,此三种人不能错过
查看>>
用VC++开发Oracle数据库应用程序详解2
查看>>
bzoj1305
查看>>
SpringAOP面向切面编程
查看>>
[USACO12JAN]Video Game Combos
查看>>
Multiset的使用 TOJ 2196.Nuanran's Idol II 与 UVA11136 Hoax or what
查看>>