NDK38

it2024-05-14  45

NDK开发汇总

文章目录

一 集成faac1 下载编译faac生成静态库和.h头文件2 项目添加libfaac.a和.h头文件,CmakeList: 二 获取音频AudioChannelLivePusher 三 音频解码与推送native-libAudioChannel 四 Demo

一 集成faac

1 下载编译faac生成静态库和.h头文件

2 项目添加libfaac.a和.h头文件,CmakeList:

cmake_minimum_required(VERSION 3.4.1) # 引入指定目录下的CMakeLists.txt add_subdirectory(src/main/cpp/librtmp) add_library( native-lib SHARED src/main/cpp/native-lib.cpp src/main/cpp/VideoChannel.cpp src/main/cpp/AudioChannel.cpp) include_directories(src/main/cpp/include) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/src/main/cpp/libs/${ANDROID_ABI}") target_link_libraries( native-lib rtmp x264 faac log)

二 获取音频

AudioChannel

public class AudioChannel { private int inputSamples; private ExecutorService executor; private AudioRecord audioRecord; private LivePusher mLivePusher; private int channels = 1; private boolean isLiving; public AudioChannel(LivePusher livePusher) { mLivePusher = livePusher; executor = Executors.newSingleThreadExecutor(); //准备录音机 采集pcm 数据 int channelConfig; if (channels == 2) { channelConfig = AudioFormat.CHANNEL_IN_STEREO; } else { channelConfig = AudioFormat.CHANNEL_IN_MONO; } mLivePusher.native_setAudioEncInfo(44100, channels); //16 位 2个字节 inputSamples = mLivePusher.getInputSamples() * 2; //最小需要的缓冲区 int minBufferSize = AudioRecord.getMinBufferSize(44100, channelConfig, AudioFormat.ENCODING_PCM_16BIT) * 2; //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()); } public void stopLive() { isLiving = false; } public void release() { audioRecord.release(); } 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(); } } }

LivePusher

public class LivePusher { private AudioChannel audioChannel; private VideoChannel videoChannel; public LivePusher(Activity activity, int width, int height, int bitrate, int fps, int cameraId) { native_init(); videoChannel = new VideoChannel(this,activity, width, height, bitrate, fps, cameraId); audioChannel = new AudioChannel(this); } public void setPreviewDisplay(SurfaceHolder surfaceHolder) { videoChannel.setPreviewDisplay(surfaceHolder); } public void switchCamera() { videoChannel.switchCamera(); } public void startLive(String path) { native_start(path); videoChannel.startLive(); audioChannel.startLive(); } public void stopLive(){ videoChannel.stopLive(); audioChannel.stopLive(); native_stop(); } public void release(){ videoChannel.release(); audioChannel.release(); native_release(); } public native void native_init(); public native void native_start(String path); public native void native_setVideoEncInfo(int width, int height, int fps, int bitrate); public native void native_setAudioEncInfo(int sampleRateInHz, int channelConfig); public native void native_pushVideo(byte[] data); public native void native_pushAudio(byte[] data); public native int getInputSamples(); public native void native_stop(); public native void native_release(); }

三 音频解码与推送

native-lib

#include <jni.h> #include <string> #include "safe_queue.h" #include "librtmp/rtmp.h" #include "VideoChannel.h" #include "AudioChannel.h" #include "macro.h" SafeQueue<RTMPPacket *> packets; VideoChannel *videoChannel = 0; int isStart = 0; pthread_t pid; int readyPushing = 0; uint32_t start_time; AudioChannel *audioChannel = 0; void releasePackets(RTMPPacket *&packet) { if (packet) { RTMPPacket_Free(packet); delete packet; packet = 0; } } void callback(RTMPPacket *packet) { if (packet) { //设置时间戳 packet->m_nTimeStamp = RTMP_GetTime() - start_time; packets.push(packet); } } extern "C" JNIEXPORT void JNICALL Java_com_cn_ray_rtmpdump_LivePusher_native_1init(JNIEnv *env, jobject instance) { //准备一个Video编码器的工具类 :进行编码 videoChannel = new VideoChannel; videoChannel->setVideoCallback(callback); audioChannel = new AudioChannel; audioChannel->setAudioCallback(callback); //准备一个队列,打包好的数据 放入队列,在线程中统一的取出数据再发送给服务器 packets.setReleaseCallback(releasePackets); } extern "C" JNIEXPORT void JNICALL Java_com_cn_ray_rtmpdump_LivePusher_native_1setVideoEncInfo(JNIEnv *env, jobject instance, jint width, jint height, jint fps, jint bitrate) { if (videoChannel) { videoChannel->setVideoEncInfo(width, height, fps, bitrate); } } void *start(void *args) { char *url = static_cast<char *>(args); RTMP *rtmp = 0; do { rtmp = RTMP_Alloc(); if (!rtmp) { LOGE("alloc rtmp失败"); break; } RTMP_Init(rtmp); int ret = RTMP_SetupURL(rtmp, url); if (!ret) { LOGE("设置地址失败:%s", url); break; } //5s超时时间 rtmp->Link.timeout = 5; RTMP_EnableWrite(rtmp); ret = RTMP_Connect(rtmp, 0); if (!ret) { LOGE("连接服务器:%s", url); break; } ret = RTMP_ConnectStream(rtmp, 0); if (!ret) { LOGE("连接流:%s", url); break; } //记录一个开始时间 start_time = RTMP_GetTime(); //表示可以开始推流了 readyPushing = 1; packets.setWork(1); //保证第一个数据是 aac解码数据包 callback(audioChannel->getAudioTag()); RTMPPacket *packet = 0; while (readyPushing) { packets.pop(packet); if (!readyPushing) { break; } if (!packet) { continue; } packet->m_nInfoField2 = rtmp->m_stream_id; //发送rtmp包 1:队列 // 意外断网?发送失败,rtmpdump 内部会调用RTMP_Close // RTMP_Close 又会调用 RTMP_SendPacket // RTMP_SendPacket 又会调用 RTMP_Close // 将rtmp.c 里面WriteN方法的 Rtmp_Close注释掉 ret = RTMP_SendPacket(rtmp, packet, 1); releasePackets(packet); if (!ret) { LOGE("发送失败"); break; } } releasePackets(packet); } while (0); // isStart = 0; readyPushing = 0; packets.setWork(0); packets.clear(); if (rtmp) { RTMP_Close(rtmp); RTMP_Free(rtmp); } delete (url); return 0; } extern "C" JNIEXPORT void JNICALL Java_com_cn_ray_rtmpdump_LivePusher_native_1start(JNIEnv *env, jobject instance, jstring path_) { if (isStart) { return; } isStart = 1; const char *path = env->GetStringUTFChars(path_, 0); char *url = new char[strlen(path) + 1]; strcpy(url, path); pthread_create(&pid, 0, start, url); env->ReleaseStringUTFChars(path_, path); } extern "C" JNIEXPORT void JNICALL Java_com_cn_ray_rtmpdump_LivePusher_native_1pushVideo(JNIEnv *env, jobject instance, jbyteArray data_) { if (!videoChannel || !readyPushing) { return; } jbyte *data = env->GetByteArrayElements(data_, NULL); videoChannel->encodeData(data); env->ReleaseByteArrayElements(data_, data, 0); } extern "C" JNIEXPORT void JNICALL Java_com_cn_ray_rtmpdump_LivePusher_native_1stop(JNIEnv *env, jobject instance) { readyPushing = 0; //关闭队列工作 packets.setWork(0); pthread_join(pid, 0); } extern "C" JNIEXPORT void JNICALL Java_com_cn_ray_rtmpdump_LivePusher_native_1release(JNIEnv *env, jobject instance) { DELETE(videoChannel); DELETE(audioChannel); } extern "C" JNIEXPORT void JNICALL Java_com_cn_ray_rtmpdump_LivePusher_native_1setAudioEncInfo(JNIEnv *env, jobject instance, jint sampleRateInHz, jint channelConfig) { if(audioChannel){ audioChannel->setAudioEncInfo(sampleRateInHz,channelConfig); } }extern "C" JNIEXPORT jint JNICALL Java_com_cn_ray_rtmpdump_LivePusher_getInputSamples(JNIEnv *env, jobject instance) { if(audioChannel){ audioChannel->getInputSamples(); } return -1; } extern "C" JNIEXPORT void JNICALL Java_com_cn_ray_rtmpdump_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); }

AudioChannel

// #include <cstring> #include "AudioChannel.h" #include "macro.h" AudioChannel::AudioChannel() { } AudioChannel::~AudioChannel() { DELETE(buffer); //释放编码器 if (audioCodec) { faacEncClose(audioCodec); audioCodec = 0; } } void AudioChannel::setAudioCallback(AudioCallback audioCallback) { this->audioCallback = audioCallback; } 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]; } int AudioChannel::getInputSamples() { return inputSamples; } RTMPPacket *AudioChannel::getAudioTag() { u_char *buf; u_long len; faacEncGetDecoderSpecificInfo(audioCodec, &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; //图片数据 memcpy(&packet->m_body[2], buf, len); 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; return packet; } 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); } }

四 Demo

RTMPDump

最新回复(0)