当前位置:   article > 正文

Android-音视频学习系列-(九)Android-端实现-rtmp-推流_android rtmp

android rtmp

public void surfaceCreated(SurfaceHolder holder) {
Log.d(SopCastConstant.TAG, “SurfaceView created”);
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.d(SopCastConstant.TAG, “SurfaceView width:” + width + " height:" + height);
CameraHolder.instance().openCamera();
CameraHolder.instance().startPreview();
}
};

5. 停止预览

停止预览只需要释放掉相机资源即可:

public synchronized void releaseCamera() {
if (mState == State.PREVIEW) {
stopPreview();
}
if (mState != State.OPENED) {
return;
}
if (mCameraDevice == null) {
return;
}
mCameraDevice.release();
mCameraDevice = null;
mCameraData = null;
mState = State.INIT;
}

音频编码

AudioRecord 采集完之后需要对 PCM 数据进行实时的编码 (软编利用 libfaac 通过 NDK 交叉编译静态库、硬编使用 Android SDK MediaCodec 进行编码)。

软编

语音软编这里们用主流的编码库 libfaac 进行编码 AAC 语音格式数据。

1. 编译 libfaac
1.1 下载 libfaac

wget https://sourceforge.net/projects/faac/files/faac-src/faac-1.29/faac-1.29.9.2.tar.gz

1.2 编写交叉编译脚本

#!/bin/bash

#打包地址
PREFIX=pwd/android/armeabi-v7a
#配置NDK 环境变量
NDK_ROOT=KaTeX parse error: Expected 'EOF', got '#' at position 10: NDK_HOME #̲指定 CPU CPU=arm-…NDK_ROOT/toolchains/$CPU-4.9/prebuilt/linux-x86_64

FLAGS=“-isysroot $NDK_ROOT/sysroot -isystem KaTeX parse error: Expected group after '_' at position 54: …-androideabi -D_̲_ANDROID_API__=ANDROID_API -U_FILE_OFFSET_BITS -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 -O0 -fPIC”

CROSS_COMPILE= T O O L C H A I N / b i n / a r m − l i n u x − a n d r o i d e a b i e x p o r t C C = " TOOLCHAIN/bin/arm-linux-androideabi export CC=" TOOLCHAIN/bin/armlinuxandroideabiexportCC="CROSS_COMPILE-gcc --sysroot= N D K R O O T / p l a t f o r m s / a n d r o i d − 17 / a r c h − a r m " e x p o r t C F L A G S = " NDK_ROOT/platforms/android-17/arch-arm" export CFLAGS=" NDKROOT/platforms/android17/archarm"exportCFLAGS="FLAGS"

./configure
–prefix=$PREFIX
–host=arm-linux
–with-pic
–enable-shared=no

make clean
make install

2. CMakeLists.txt 配置

cmake_minimum_required(VERSION 3.4.1)
#语音编码器
set(faac KaTeX parse error: Expected 'EOF', got '#' at position 26: …RCE_DIR}/faac) #̲加载 faac 头文件目录 i…{faac}/include)
#指定 faac 静态库文件目录
set(CMAKE_CXX_FLAGS “ C M A K E C X X F L A G S − L {CMAKE_CXX_FLAGS} -L CMAKECXXFLAGSL{faac}/libs/${CMAKE_ANDROID_ARCH_ABI}”)
#批量添加自己编写的 cpp 文件,不要把 .h 加入进来了
file(GLOB Push_CPP ${ykpusher}/
.cpp)
#添加自己编写 cpp 源文件生成动态库
add_library(ykpusher SHARED ${Push_CPP})
#找系统中 NDK log库
find_library(log_lib
log)
#推流 so
target_link_libraries(
#播放 so
ykpusher

# 写了此命令不用在乎添加 ffmpeg lib 顺序问题导致应用崩溃

-Wl,–start-group

avcodec avfilter avformat avutil swresample swscale

-Wl,–end-group

z

#推流库
rtmp
#视频编码
x264
#语音编码
faac
#本地库
android
${log_lib}
)

3. 配置 faac 编码参数

//设置语音软编码参数
void AudioEncoderChannel::setAudioEncoderInfo(int samplesHZ, int channel) {
//如果已经初始化,需要释放
release();
//通道 默认单声道
mChannels = channel;
//打开编码器
//3、一次最大能输入编码器的样本数量 也编码的数据的个数 (一个样本是16位 2字节)
//4、最大可能的输出数据 编码后的最大字节数
mAudioCodec = faacEncOpen(samplesHZ, channel, &mInputSamples, &mMaxOutputBytes);
if (!mAudioCodec) {
if (mIPushCallback) {
mIPushCallback->onError(THREAD_MAIN, FAAC_ENC_OPEN_ERROR);
}
return;
}

//设置编码器参数
faacEncConfigurationPtr config = faacEncGetCurrentConfiguration(mAudioCodec);
//指定为 mpeg4 标准
config->mpegVersion = MPEG4;
//lc 标准
config->aacObjectType = LOW;
//16位
config->inputFormat = FAAC_INPUT_16BIT;
// 编码出原始数据 既不是adts也不是adif
config->outputFormat = 0;
faacEncSetConfiguration(mAudioCodec, config);
//输出缓冲区 编码后的数据 用这个缓冲区来保存
mBuffer = new u_char[mMaxOutputBytes];
//设置一个标志,用于开启编码
isStart = true;
}

4. 配置 AAC 包头

在发送 rtmp 音视频包的时候需要将语音包头第一个发送

/**

  • 音频头包数据
  • @return
    */
    RTMPPacket *AudioEncoderChannel::getAudioTag() {
    if (!mAudioCodec) {
    setAudioEncoderInfo(FAAC_DEFAUTE_SAMPLE_RATE, FAAC_DEFAUTE_SAMPLE_CHANNEL);
    if (!mAudioCodec)return 0;
    }
    u_char *buf;
    u_long len;
    faacEncGetDecoderSpecificInfo(mAudioCodec, &buf, &len);
    int bodySize = 2 + len;
    RTMPPacket *packet = new RTMPPacket;
    RTMPPacket_Alloc(packet, bodySize);
    //双声道
    packet->m_body[0] = 0xAF;
    if (mChannels == 1) { //单身道
    packet->m_body[0] = 0xAE;
    }
    packet->m_body[1] = 0x00;
    //将包头数据 copy 到RTMPPacket 中
    memcpy(&packet->m_body[2], buf, len);
    //是否使用绝对时间戳
    packet->m_hasAbsTimestamp = FALSE;
    //包大小
    packet->m_nBodySize = bodySize;
    //包类型
    packet->m_packetType = RTMP_PACKET_TYPE_AUDIO;
    //语音通道
    packet->m_nChannel = 0x11;
    packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
    return packet;
    }
5. 开始实时编码

void AudioEncoderChannel::encodeData(int8_t *data) {
if (!mAudioCodec || !isStart)//不符合编码要求,退出
return;
//返回编码后的数据字节长度
int bytelen = faacEncEncode(mAudioCodec, reinterpret_cast<int32_t *>(data), mInputSamples,mBuffer, mMaxOutputBytes);
if (bytelen > 0) {
//开始打包 rtmp
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], mBuffer, bytelen);

packet->m_hasAbsTimestamp = FALSE;
packet->m_nBodySize = bodySize;
packet->m_packetType = RTMP_PACKET_TYPE_AUDIO;
packet->m_nChannel = 0x11;
packet->m_headerType = RTMP_PACKET_SIZE_LARGE;
//发送 rtmp packet,回调给 RTMP send 模块
mAudioCallback(packet);
}
}

6. 释放编码器

在不需要编码或者退出编码的时候需要主动释放编码器,释放 native 内存,可以通过如下函数来实现释放编码器的操作:

void AudioEncoderChannel::release() {
//退出编码的标志
isStart = false;
//释放编码器
if (mAudioCodec) {
//关闭编码器
faacEncClose(mAudioCodec);
//释放缓冲区
DELETE(mBuffer);
mAudioCodec = 0;
}
}

硬编

软编码介绍完了下面利用 Android SDK 自带的 MediaCodec 函数进行对 PCM 编码为 AAC 的格式音频数据。使用 MediaCodec 编码 AAC 对 Android 系统是有要求的,必须是 4.1系统以上,即要求 Android 的版本代号在 Build.VERSION_CODES.JELLY_BEAN (16) 以上。MediaCodec 是 Android 系统提供的硬件编码器,它可以利用设备的硬件来完成编码,从而大大提高编码的效率,还可以降低电量的使用,但是其在兼容性方面不如软编号,因为 Android 设备的锁片化太严重,所以读者可以自己衡量在应用中是否使用 Android 平台的硬件编码特性。

1. 创建 "audio/mp4a-latm" 类型的硬编码器

mediaCodec = MediaCodec.createEncoderByType(configuration.mime);

2. 配置音频硬编码器

public static MediaCodec getAudioMediaCodec(AudioConfiguration configuration){
MediaFormat format = MediaFormat.createAudioFormat(configuration.mime, configuration.frequency, configuration.channelCount);
if(configuration.mime.equals(AudioConfiguration.DEFAULT_MIME)) {
format.setInteger(MediaFormat.KEY_AAC_PROFILE, configuration.aacProfile);
}
//语音码率
format.setInteger(MediaFormat.KEY_BIT_RATE, configuration.maxBps * 1024);
//语音采样率 44100
format.setInteger(MediaFormat.KEY_SAMPLE_RATE, configuration.frequency);
int maxInputSize = AudioUtils.getRecordBufferSize(configuration);
format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, maxInputSize);
format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, configuration.channelCount);

MediaCodec mediaCodec = null;
try {
mediaCodec = MediaCodec.createEncoderByType(configuration.mime);
//MediaCodec.CONFIGURE_FLAG_ENCODE 代表编码器,解码传 0 即可
mediaCodec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
} catch (Exception e) {
e.printStackTrace();
if (mediaCodec != null) {
mediaCodec.stop();
mediaCodec.release();
mediaCodec = null;
}
}
return mediaCodec;
}

3. 开启音频硬编码器

void prepareEncoder() {
mMediaCodec = AudioMediaCodec.getAudioMediaCodec(mAudioConfiguration);
mMediaCodec.start();
}

4. 拿到硬编码输入(PCM)输出(AAC) ByteBufferer

到了这一步说明,音频编码器配置完成并且也成功开启了,现在就可以从 MediaCodec 实例中获取两个 buffer ,一个是输入 buffer 一个是输出 buffer , 输入 buffer 类似于 FFmpeg 中的 AVFrame 存放待编码的 PCM 数据,输出 buffer 类似于 FFmpeg 的 AVPacket 编码之后的 AAC 数据, 其代码如下:

//存放的是 PCM 数据
ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();
//存放的是编码之后的 AAC 数据
ByteBuffer[] outputBuffers = mMediaCodec.getOutputBuffers();

5. 开始 PCM 硬编码为 AAC

到此,所有初始化方法已实现完毕,下面来看一下 MediaCodec 的工作原理如下图所示,左边 Client 元素代表要将 PCM 放到 inputBuffer 中的某个具体的 buffer 中去,右边的 Client 元素代表将编码之后的原始 AAC 数据从 outputBuffer 中的某个具体 buffer 中取出来,

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/木道寻08/article/detail/761340
推荐阅读
相关标签