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();
        }
相关推荐
红米饭配南瓜汤2 小时前
Android显示系统(04)- OpenGL ES - Shader绘制三角形
android·音视频·媒体
码农老张Zy2 小时前
【PHP小课堂】学习PHP中的变量处理相关操作
android·开发语言·学习·php
尹中文2 小时前
Android ConstraintLayout 约束布局的使用手册
android
苗壮.2 小时前
Android 俩个主题的不同之处 “Theme.AppCompat vs android:Theme.Material.Light.NoActionBar”
android·gitee·appcompat
努力进修2 小时前
【Java-数据结构篇】Java 中栈和队列:构建程序逻辑的关键数据结构基石
android·java·数据结构
闲暇部落4 小时前
OpenGL ES详解——文字渲染
android·freetype·文字渲染·位图字体
画个太阳作晴天9 小时前
Android10 设备死机的问题分析和解决
android·framework·anr
SHUIPING_YANG12 小时前
Typora设置自动上传图片到图床
android
Ai 编码助手12 小时前
php多进程那点事,用 swoole 如何去解决呢
android·php·swoole
alexhilton14 小时前
群星闪耀的大前端开发
android·kotlin·android jetpack