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

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 中取出来,👈 左边的小方块代表各个 inputBuffer 元素,右边的小方块则代表各个 outputBuffer 元素。详细介绍可以看 MediaCodec 类介绍

代码具体实现如下:

//input:PCM
synchronized void offerEncoder(byte[] input) {
if(mMediaCodec == null) {
return;
}
ByteBuffer[] inputBuffers = mMediaCodec.getInputBuffers();
ByteBuffer[] outputBuffers = mMediaCodec.getOutputBuffers();
int inputBufferIndex = mMediaCodec.dequeueInputBuffer(12000);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
inputBuffer.clear();
inputBuffer.put(input);
mMediaCodec.queueInputBuffer(inputBufferIndex, 0, input.length, 0, 0);
}

int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(mBufferInfo, 12000);
while (outputBufferIndex >= 0) {
ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
if(mListener != null) {
//将 AAC 数据回调出去
mListener.onAudioEncode(outputBuffer, mBufferInfo);
}
//释放当前内部编码内存
mMediaCodec.releaseOutputBuffer(outputBufferIndex, false);
outputBufferIndex = mMediaCodec.dequeueOutputBuffer(mBufferInfo, 0);
}
}

6. AAC 打包为 flv

@Override
public void onAudioData(ByteBuffer bb, MediaCodec.BufferInfo bi) {
if (packetListener == null || !isHeaderWrite || !isKeyFrameWrite) {
return;
}
bb.position(bi.offset);
bb.limit(bi.offset + bi.size);

byte[] audio = new byte[bi.size];
bb.get(audio);
int size = AUDIO_HEADER_SIZE + audio.length;
ByteBuffer buffer = ByteBuffer.allocate(size);
FlvPackerHelper.writeAudioTag(buffer, audio, false, mAudioSampleSize);
packetListener.onPacket(buffer.array(), AUDIO);
}

public static void writeAudioTag(ByteBuffer buffer, byte[] audioInfo, boolean isFirst, int audioSize) {
//写入音频头信息
writeAudioHeader(buffer, isFirst, audioSize);

//写入音频信息
buffer.put(audioInfo);
}
复制代码

7. 释放编码器

在使用完 MediaCodec 编码器之后,就需要停止运行并释放编码器,代码如下:

synchronized public void stop() {

  • 27
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值