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

需求

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

技术

因为我们不涉及直播,也不涉及视频,工期也比较短,只是音频推流,所以没用更复杂的ffmpeg,使用了更简单的Librtmp,灵感来源于这篇文章
使用MediaCodec和RTMP做直播推流

但是这个的坏处是封装太深了,方便确实方便,不太好提取,我这边只是作为参考

思路

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

PCMToAAC:

java 复制代码
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数据填充给inputBuffer
            mediaEncode.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];//拿到输出Buffer
                outputBuffer.position(encodeBufferInfo.offset);
                outputBuffer.limit(encodeBufferInfo.offset + outBitSize);
                chunkAudio = new byte[outPacketSize];
                addADTStoPacket(chunkAudio, outPacketSize);//添加ADTS
                outputBuffer.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 LC
        int freqIdx = 8; // 16KHz
        int chanCfg = 1; // CPE

        // fill in ADTS data
        packet[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 涉及的操作较多,所以进入页面会有卡顿,可以在子线程初始化

使用

java 复制代码
 PCMToAAC pcmToAAC;
 .....
   new Thread(new Runnable() {
            @Override
            public void run() {
                pcmToAAC = new PCMToAAC();
            }
        }).start();

    @Override
    protected void onDestroy() {
        super.onDestroy();

        pcmToAAC.closeRtmp();
    }

/**
戒指录音回传
**/
  @Override
    public void CONTROL_AUDIO(byte[] bytes) {
   
        new Thread(new Runnable() {
            @Override
            public void run() {


                try {
                    pcmToAAC.dstAudioFormatFromPCM(bytes);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
        }
相关推荐
y = xⁿ6 分钟前
MySQL八股知识合集
android·mysql·adb
andr_gale41 分钟前
04_rc文件语法规则
android·framework·aosp
祖国的好青年2 小时前
VS Code 搭建 React Native 开发环境(Windows 实战指南)
android·windows·react native·react.js
黄林晴2 小时前
警惕!AGP 9.2 别只改版本号,R8 规则与构建链路全线收紧
android·gradle
小米渣的逆袭2 小时前
Android ADB 完全使用指南
android·adb
儿歌八万首3 小时前
Jetpack Compose Canvas 进阶:结合 animateFloatAsState 让自定义图形动起来
android·动画·compose
zhangphil3 小时前
Android Page 3 Flow读sql数据库媒体文件,Kotlin
android·kotlin
神探小白牙4 小时前
echarts,3d堆叠图
android·3d·echarts
李白的天不白4 小时前
如何项目发布到github上
android·vue.js