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();
        }
相关推荐
千里马学框架1 天前
一起学 Android 14:ShellTransition 屏幕旋转过程深度剖析
android·智能手机·性能优化·framework·性能·屏幕旋转·rotation
美狐美颜SDK开放平台1 天前
开发直播APP时如何接入视频美颜SDK?开发流程与注意事项
android·人工智能·计算机视觉·音视频·直播美颜sdk
AFinalStone1 天前
Android7 SystemUI源码解析(七)Keyguard锁屏模块深度解析
android·systemui
致远ccc1 天前
Google Play 上架前如何测试 App?多国家 Android 环境测试
android·app测试·googleplay·多国家应用测试
ttyyttemo1 天前
Kotlin 协程中的 Job 结构化并发与取消
android
sun0077001 天前
tbox 4g/5g切换,导致wan ip 改变,导致车机旧网络不可用。需要重启车机才行
android
其实防守也摸鱼1 天前
内网穿透与反向代理:原理、工具与实战指南
android·大数据·运维·安全·网络安全·自动化·渗透
AFinalStone2 天前
Android7 SystemUI 源码解析(四)NavigationBar 导航栏与 SystemBars
android·systemui
JMchen2 天前
属性动画原理与高级动画实现
android·kotlin·canvas
AFinalStone2 天前
Android7 SystemUI 源码解析(二)启动流程深度解析
android·systemui