android将pcm byte[]通过Librtmp进行rtmp推流

ops/2024/12/1 8:33:11/

需求

我们这边做的功能是智能戒指,戒指可以录音,然后app通过蓝牙连接,将音频的byte[]进行rtmp推流

技术

因为我们不涉及直播,也不涉及视频,工期也比较短,只是音频推流,所以没用更复杂的ffmpeg,使用了更简单的Librtmp,灵感来源于这篇文章
使用MediaCodec和RTMP做直播推流
但是这个的坏处是封装太深了,方便确实方便,不太好提取,我这边只是作为参考

思路

戒指上传的音频,或者手机录音的音频,都是pcm的,需要将先转换为aac格式的,所以必现先实现转换,然后再写入aac文件的时候,进行推流。
Librtmp Client for Android

PCMToAAC:

public class PCMToAAC {private String encodeType = MediaFormat.MIMETYPE_AUDIO_AAC;private static final int samples_per_frame = 2048;private MediaCodec mediaEncode;private MediaCodec.BufferInfo encodeBufferInfo;private ByteBuffer[] encodeInputBuffers;private ByteBuffer[] encodeOutputBuffers;private byte[] chunkAudio = new byte[0];private BufferedOutputStream out;File aacFile;File pcmFile;private RTMPMuxer rtmpMuxer=new RTMPMuxer();public PCMToAAC(String aacPath, String pcmPath) {aacFile = new File(aacPath);pcmFile = new File(pcmPath);if (!aacFile.exists()) {try {aacFile.getParentFile().mkdirs();aacFile.createNewFile();} catch (Exception e) {e.printStackTrace();}}try {out = new BufferedOutputStream(new FileOutputStream(aacFile, false));} catch (FileNotFoundException e) {e.printStackTrace();}initAACMediaEncode();rtmpMuxer.open("rtmp://4xxxx/live/stream",100,100);}public PCMToAAC() {aacFile = new File(com.smart.bing.utils.FileUtil.getSDPath(App.getInstance(),  "test.aac"));try{aacFile.delete();}catch (Exception e){e.printStackTrace();}if (!aacFile.exists()) {try {aacFile.getParentFile().mkdirs();aacFile.createNewFile();} catch (Exception e) {e.printStackTrace();}}try {out = new BufferedOutputStream(new FileOutputStream(aacFile, false));} catch (FileNotFoundException e) {e.printStackTrace();}initAACMediaEncode();rtmpMuxer.open("rtmp://xxxx/live/stream",100,100);}/*** 初始化AAC编码器*/private void initAACMediaEncode() {try {//参数对应-> mime type、采样率、声道数MediaFormat encodeFormat = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 16000, 1);encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, 64000);//比特率encodeFormat.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);encodeFormat.setInteger(MediaFormat.KEY_CHANNEL_MASK, AudioFormat.CHANNEL_IN_MONO);encodeFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, samples_per_frame);//作用于inputBuffer的大小mediaEncode = MediaCodec.createEncoderByType(encodeType);mediaEncode.configure(encodeFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);} catch (IOException e) {e.printStackTrace();}if (mediaEncode == null) {return;}mediaEncode.start();encodeInputBuffers = mediaEncode.getInputBuffers();encodeOutputBuffers = mediaEncode.getOutputBuffers();encodeBufferInfo = new MediaCodec.BufferInfo();}/*** 编码PCM数据 得到AAC格式的音频文件*/public void dstAudioFormatFromPCM(byte[] pcmData) {int inputIndex;ByteBuffer inputBuffer;int outputIndex;ByteBuffer outputBuffer;int outBitSize;int outPacketSize;byte[] PCMAudio;PCMAudio = pcmData;encodeInputBuffers = mediaEncode.getInputBuffers();encodeOutputBuffers = mediaEncode.getOutputBuffers();encodeBufferInfo = new MediaCodec.BufferInfo();inputIndex = mediaEncode.dequeueInputBuffer(0);if (inputIndex != -1) {inputBuffer = encodeInputBuffers[inputIndex];inputBuffer.clear();inputBuffer.limit(PCMAudio.length);inputBuffer.put(PCMAudio);//PCM数据填充给inputBuffermediaEncode.queueInputBuffer(inputIndex, 0, PCMAudio.length, 0, 0);//通知编码器 编码outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 0);while (outputIndex > 0) {outBitSize = encodeBufferInfo.size;outPacketSize = outBitSize + 7;//7为ADT头部的大小outputBuffer = encodeOutputBuffers[outputIndex];//拿到输出BufferoutputBuffer.position(encodeBufferInfo.offset);outputBuffer.limit(encodeBufferInfo.offset + outBitSize);chunkAudio = new byte[outPacketSize];addADTStoPacket(chunkAudio, outPacketSize);//添加ADTSoutputBuffer.get(chunkAudio, 7, outBitSize);//将编码得到的AAC数据 取出到byte[]中try {//录制aac音频文件,保存在手机内存中out.write(chunkAudio, 0, chunkAudio.length);rtmpMuxer.writeAudio(chunkAudio,0,chunkAudio.length,encodeBufferInfo.presentationTimeUs);Log.d("chunkAudio", Arrays.toString(chunkAudio));out.flush();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}outputBuffer.position(encodeBufferInfo.offset);mediaEncode.releaseOutputBuffer(outputIndex, false);outputIndex = mediaEncode.dequeueOutputBuffer(encodeBufferInfo, 0);}}}public void closeRtmp(){rtmpMuxer.close();}/*** 添加ADTS头** @param packet* @param packetLen*/private void addADTStoPacket(byte[] packet, int packetLen) {int profile = 2; // AAC LCint freqIdx = 8; // 16KHzint chanCfg = 1; // CPE// fill in ADTS datapacket[0] = (byte) 0xFF;packet[1] = (byte) 0xF1;packet[2] = (byte) (((profile - 1) << 6) + (freqIdx << 2) + (chanCfg >> 2));packet[3] = (byte) (((chanCfg & 3) << 6) + (packetLen >> 11));packet[4] = (byte) ((packetLen & 0x7FF) >> 3);packet[5] = (byte) (((packetLen & 7) << 5) + 0x1F);packet[6] = (byte) 0xFC;}public byte[] readInputStream() {InputStream inputStream = null;try {inputStream = new FileInputStream(pcmFile);} catch (FileNotFoundException e) {e.printStackTrace();}// 1.建立通道对象ByteArrayOutputStream outputStream = new ByteArrayOutputStream();// 2.定义存储空间byte[] buffer = new byte[1024];// 3.开始读文件int len = -1;try {if (inputStream != null) {while ((len = inputStream.read(buffer)) != -1) {// 将Buffer中的数据写到outputStream对象中
//                    outputStream.write(buffer, 0, len);dstAudioFormatFromPCM(buffer);Log.e("wqs+readInputStream", "readInputStream: " + buffer);}}// 4.关闭流outputStream.close();inputStream.close();} catch (IOException e) {e.printStackTrace();}return outputStream.toByteArray();}}

坑1:

rtmpMuxer.open(“rtmp://xxxx/live/stream”,100,100);

要放在外层,千万不要和rtmpMuxer.writeAudio放一起,要不然会卡死
坑2:
推流的地址,后边要加上音频流的名称,名字可以随便取,我这边叫stream
坑3:
这个PCMToAAC 涉及的操作较多,所以进入页面会有卡顿,可以在子线程初始化

使用

 PCMToAAC pcmToAAC;.....new Thread(new Runnable() {@Overridepublic void run() {pcmToAAC = new PCMToAAC();}}).start();@Overrideprotected void onDestroy() {super.onDestroy();pcmToAAC.closeRtmp();}/**
戒指录音回传
**/@Overridepublic void CONTROL_AUDIO(byte[] bytes) {new Thread(new Runnable() {@Overridepublic void run() {try {pcmToAAC.dstAudioFormatFromPCM(bytes);} catch (Exception e) {e.printStackTrace();}}}).start();}

http://www.ppmy.cn/ops/138146.html

相关文章

【MySQL-6】MySQL的复合查询

目录 1. 整体学习的思维导图 2. 回顾基本查询 3. 多表查询 4. 自连接 5. 子查询 5.1 单行子查询 5.2 多行子查询 5.3 多列子查询 5.4 在from子句中使用子查询 6. 合并查询 1. 整体学习的思维导图 2. 回顾基本查询 使用scott数据库中的表&#xff0c;完成以下查询&am…

Ansible自动化一键部署单节点集群架构

自动化部署利器&#xff1a;Ansible 一键部署脚本 在现代IT基础设施管理中&#xff0c;Ansible以其简洁、强大的自动化能力脱颖而出。以下是精心打造的Ansible自动化一键部署脚本&#xff0c;旨在简化部署流程&#xff0c;提升效率&#xff0c;确保一致性和可靠性。 通过这个…

模拟器快速上手,助力HarmonyOS应用/服务高效开发

文章目录 1 创建模拟器1&#xff09;打开设备管理界面2&#xff09;设置本地模拟器实例存储路径3&#xff09;创建一个模拟器&#xff08;1&#xff09;选择模拟器设备&#xff08;2&#xff09;创建模拟器&#xff08;3&#xff09;启动模拟器&#xff08;4&#xff09;关闭模…

微服务篇-微服务保护:使用 Sentinel 来实现请求限流、线程隔离、服务熔断和 Fallback 备用方案的使用

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 微服务保护 1.1 请求限流方案 1.2 线程隔离方案 1.3 服务熔断方案 2.0 Sentinel 2.1 Sentinel 安装 2.2 微服务整合 3.0 Sentinel-请求限流 4.0 Sentinel-线程隔离…

射频ADS匹配器阻抗仿真KEYSIGHT FR Basic:Efficient Impedance Matching With ADS

射频ADS匹配器阻抗仿真KEYSIGHT FR Basic&#xff1a;Efficient Impedance Matching With ADS

TCP 的三次握手

TCP 的三次握手 1. TCP 三次握手&#xff08;Three-Way Handshake&#xff09; 目的&#xff1a;三次握手的目的是为了在客户端和服务端之间建立可靠的 TCP 连接&#xff0c;确保双方能够同步&#xff0c;并且为数据传输做好准备。 三次握手的过程&#xff1a; 第一次握手&am…

【论文笔记】Frequency Domain Model Augmentation for Adversarial Attack

Abstract 对于黑盒攻击&#xff0c;替代模型与受害模型之间的差距通常较大&#xff0c;表现为较弱的攻击性能。基于对抗样本的可迁移性可以通过同时攻击不同模型来提高的观察&#xff0c;提出了利用变换图像模拟不同模型的模型增强方法。 然而&#xff0c;现有的空间域变换并没…

小F的矩阵值调整

问题描述 小F得到了一个矩阵。如果矩阵中某一个格子的值是偶数&#xff0c;则该值变为它的三倍&#xff1b;如果是奇数&#xff0c;则保持不变。小F想知道调整后的矩阵是什么样子的。 测试样例 样例1&#xff1a; 输入&#xff1a;a [[1, 2, 3], [4, 5, 6]] 输出&#xff1a…