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();
        }
相关推荐
openinstall全渠道统计42 分钟前
免填邀请码工具:赋能六大核心场景,重构App增长新模型
android·ios·harmonyos
双鱼大猫1 小时前
一句话说透Android里面的ServiceManager的注册服务
android
双鱼大猫1 小时前
一句话说透Android里面的SystemServer进程的作用
android
双鱼大猫1 小时前
一句话说透Android里面的View的绘制流程和实现原理
android
双鱼大猫2 小时前
一句话说透Android里面的Window的内部机制
android
双鱼大猫2 小时前
一句话说透Android里面的为什么要设计Window?
android
双鱼大猫2 小时前
一句话说透Android里面的主线程创建时机,frameworks层面分析
android
苏金标3 小时前
android 快速定位当前页面
android
雾里看山6 小时前
【MySQL】内置函数
android·数据库·mysql
风浅月明6 小时前
[Android]页面间传递model列表
android