Android录制音频并使用ijkplayer播放

这篇具有很好参考价值的文章主要介绍了Android录制音频并使用ijkplayer播放。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1、使用MediaRecorder录音

1.1、开始录制

private MediaRecorder mMediaRecorder;
private File mTempFile;
public void startRecordAudio(Context context) {
        
        //临时文件
        if (mTmpFile == null) {
            mTmpFile = SdcardUtils.getPublicFile(context, "record/voice.aac");
        }

        Log.i("tmpFile path", mTempFile.getPath());
        final File file = mTempFile;
        if (file.exists()) {
            file.delete();
        }
        MediaRecorder recorder = mMediaRecorder;
        if (recorder == null) {
            recorder = new MediaRecorder();
            mMediaRecorder = recorder;
            
            //设置输入源
            recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
            
            //设置音频输出格式/编码格式
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                recorder.setOutputFormat(MediaRecorder.OutputFormat.AAC_ADTS);
            } else {
                recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
            }
            recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
            
            //设置音频输出路径
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                recorder.setOutputFile(file);
            } else {
                recorder.setOutputFile(file.getAbsolutePath());
            }

            try {
                //准备录制
                recorder.prepare();

                //开始录制音频
                recorder.start();
                
                requestAudioFocus();
            } catch (Exception e) {
                e.printStackTrace();
                Log.e(TAG, e.toString());
            }
        }
    }

1.2、结束录制

public File stopRecordAudio() {
        final MediaRecorder recorder = mMediaRecorder;
        if (recorder != null) {
            try {
                recorder.stop();
                recorder.release();
                mMediaRecorder = null;
            } catch (Exception e) {
                e.printStackTrace();
                Log.e(TAG, e.toString());
                return null;
            } finally {
                abandonAudioFocus();
            }
        }

        File file = mTmpFile;
        if (file != null && file.exists() && file.length() > 0) {
            return file;
        } else {
            return null;
        }
    }

2、使用AudioRecorder录音

在使用AudioRecorder时,需要了解采样率、频道配置和PCM音频格式数据的相关知识;

  1. PCM:音频的原始数据(AudioFormat.ENCODING_PCM_16BIT、AudioFormat.ENCODING_PCM_8BIT、AudioFormat.ENCODING_PCM_FLOAT等等);不同的PCM代表不同的位深
  2. 采样率:录音设备在单位时间内对模拟信号采样的多少,采样频率越高,机械波的波形就越真实越自然。常用的有16000(1.6KHz)、44100(44.1KHz)等
  3. 频道:单声道输入频道、输出声道等,相关的值有(AudioFormat.CHANNEL_IN_MONO,AudioFormat.CHANNEL_IN_STEREO等等)
//根据采样率+音频格式+频道得到录音缓存大小
int minBufferSize = AudioRecord.getMinBufferSize(16000,
                    AudioFormat.CHANNEL_IN_MONO,
                    AudioFormat.ENCODING_PCM_16BIT);

针对AudioRecord的初始化,也需要采样率、PCM原始音频格式和频道,另外还需要录音缓存大小以及录音设备,如下:

//MediaRecorder.AudioSource.MIC是麦克风录音设备,
//minBufferSize是录音缓存大小
new AudioRecord(MediaRecorder.AudioSource.MIC, 16000, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, minBufferSize);

AudioRecorder开始录音方法

recorder.startRecording();

开启子线程,通过read方法获取录音数据

while (isRecording && !recordingAudioThread.isInterrupted()) {
    //获取录音数据
    read = mAudioRecorder.read(data, 0, data.length);
    if (AudioRecord.ERROR_INVALID_OPERATION != read) {
    try {
        fos.write(data);
        Log.i("audioRecord", "写录音数据->" + read);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

2.1、开始录制(完整代码)

private AudioRecord mAudioRecorder;
private File mTempFile;
private boolean isRecording;
private Thread recordingAudioThread;

public void startRecordAudio(Context context) {
        //临时路径
        if (mTmpFile == null) {
            mTmpFile = SdcardUtils.getPublicFile(context, "record/voice.pcm");
        }

        Log.i("tmpFile path", mTmpFile.getPath());
        final File file = mTmpFile;
        if (file.exists()) {
            file.delete();
        }

        AudioRecord recorder = mAudioRecorder;
        if (recorder == null) {
            //16000是采样率,常用采样率有16000(1.6KHz),441000(44.1KHz)
            int minBufferSize = AudioRecord.getMinBufferSize(16000,
                    AudioFormat.CHANNEL_IN_MONO,
                    AudioFormat.ENCODING_PCM_16BIT);

            recorder = new AudioRecord(MediaRecorder.AudioSource.MIC, 16000, AudioFormat.CHANNEL_IN_MONO,
                    AudioFormat.ENCODING_PCM_16BIT, minBufferSize);

            mAudioRecorder = recorder;

            try {
                //开始录制音频
                isRecording = true;
                recorder.startRecording();

                recordingAudioThread = new Thread(() -> {
                    try {
                        file.createNewFile();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    FileOutputStream fos = null;
                    try {
                        fos = new FileOutputStream(file);
                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    }
                    if (fos != null) {
                        byte[] data = new byte[minBufferSize];
                        int read;

                        while (isRecording && !recordingAudioThread.isInterrupted()) {
                            read = mAudioRecorder.read(data, 0, data.length);
                            if (AudioRecord.ERROR_INVALID_OPERATION != read) {
                                try {
                                    fos.write(data);
                                    Log.i("audioRecord", "录音数据:" + read);
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }
                        }

                        try {
                            fos.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                });
                recordingAudioThread.start();

                requestAudioFocus();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

2.2、结束录制

public File stopRecordAudio() {
    isRecording = false;

    final AudioRecord audioRecord = mAudioRecorder;
    if (audioRecord != null) {
        audioRecord.stop();
        audioRecord.release();
        mAudioRecorder = null;
        recordingAudioThread.interrupt();
        recordingAudioThread = null;
    }

    File file = mTmpFile;
    if (file != null && file.exists() && file.length() > 0) {
        return file;
    } else {
        return null;
    }
}

3、PCM格式转码AAC

这个转码太难了,参考文章:Android pcm编码为aac
不过该文章中的代码有bug,当采样率为44.1KHz的时候可以转AAC,并且正常播放,但当采样率为1.6KHz的时候,转成AAC之后播放的声音极为尖锐,调整了大半天后发现是addADTStoPacket方法中freqIdx的值写死为4了

再参考了文章:Pcm 转 AAc,修复了该bug

package com.example.recordvoice.utils;

import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.os.Build;
import android.util.Log;

import androidx.annotation.RequiresApi;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class AacEncoder {
    ...
    private int sampleRateType;


    public void init(int sampleRate, int inChannel,
                     int channelCount, int sampleFormat,
                     String srcPath, String dstPath,
                     IHanlderCallback callback) {

        ...
        sampleRateType = ADTSUtils.getSampleRateType(mSampleRate);
        ...
    }

    ......
    ......
    ......

    private void addADTStoPacket(byte[] packet, int packetLen) {
        ....
        int freqIdx = sampleRateType;
        ....
    }

    static class ADTSUtils {
        private static Map<String, Integer> SAMPLE_RATE_TYPE;

        static {
            SAMPLE_RATE_TYPE = new HashMap<>();
            SAMPLE_RATE_TYPE.put("96000", 0);
            SAMPLE_RATE_TYPE.put("88200", 1);
            SAMPLE_RATE_TYPE.put("64000", 2);
            SAMPLE_RATE_TYPE.put("48000", 3);
            SAMPLE_RATE_TYPE.put("44100", 4);
            SAMPLE_RATE_TYPE.put("32000", 5);
            SAMPLE_RATE_TYPE.put("24000", 6);
            SAMPLE_RATE_TYPE.put("22050", 7);
            SAMPLE_RATE_TYPE.put("16000", 8);
            SAMPLE_RATE_TYPE.put("12000", 9);
            SAMPLE_RATE_TYPE.put("11025", 10);
            SAMPLE_RATE_TYPE.put("8000", 11);
            SAMPLE_RATE_TYPE.put("7350", 12);
        }

        public static int getSampleRateType(int sampleRate) {
            return SAMPLE_RATE_TYPE.get(sampleRate + "");
        }
    }
}

4、音频焦点

4.1、音频焦点意义

当有两个或者两个以上音频同时向同一音频输出器播放,那么声音就会混在一起,为了避免所有音乐应用同时播放,就有了“音频焦点”的概念,希望做到 一次只能有一个应用获得音频焦点

4.2、音频焦点获取

private boolean mAudioFocus = false;
private AudioFocusRequest mAudioFocusRequest;
private AbsOnAudioFocusChangeListener mOnAudioFocusChangeListener;
private android.media.AudioManager mAM;

    abstract static class AbsOnAudioFocusChangeListener implements android.media.AudioManager.OnAudioFocusChangeListener {
        boolean isEnabled = true;

        @Override
        public final void onAudioFocusChange(int focusChange) {
            if (isEnabled) {
                onChane(focusChange);
            }
        }

        abstract void onChane(int focusChane);

    }

    private synchronized void requestAudioFocus() {
        android.media.AudioManager am = mAM;

        mOnAudioFocusChangeListener = new AbsOnAudioFocusChangeListener() {
            @Override
            void onChane(int focusChane) {
                Log.i(TAG, "focusChane:" + focusChane);

                synchronized (AudioManager.this) {
                    switch (focusChane) {
                        case AUDIOFOCUS_LOSS:
                        case AUDIOFOCUS_LOSS_TRANSIENT:
                        case AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
                            if (mAudioFocus) {
                                stopPlay(true, true);
                            } else {
                                stopPlay(false, true);
                            }
                            break;
                        case AUDIOFOCUS_GAIN:
                            mAudioFocus = true;
                            break;
                    }


                }

            }

        };

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            mAudioFocusRequest = new AudioFocusRequest.Builder(AUDIOFOCUS_GAIN)
                    .setOnAudioFocusChangeListener(mOnAudioFocusChangeListener).build();
            am.requestAudioFocus(mAudioFocusRequest);
        } else {
            am.requestAudioFocus(mOnAudioFocusChangeListener, AudioStream.MODE_NORMAL, AUDIOFOCUS_GAIN);
        }

        mAudioFocus = true;
    }

4.3、放弃音频焦点

    private synchronized void abandonAudioFocus() {
        android.media.AudioManager am = mAM;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            if (mAudioFocusRequest != null) {
                am.abandonAudioFocusRequest(mAudioFocusRequest);
            }
        } else {
            if (mOnAudioFocusChangeListener != null) {
                am.abandonAudioFocus(mOnAudioFocusChangeListener);
            }
        }

        mAudioFocus = false;

    }

5、IjkPlayer

5.1、IjkPlayer简介

IjkPlayer是BiliBili基于ffmpeg进行封装的一套视频播放器框架,所以ffmpeg支持的流媒体格式和视频格式ijk都是支持的;支持Android和IOS
开源地址

5.2、IjkPlayer引入

# required, enough for most devices.
# 常用
implementation 'tv.danmaku.ijk.media:ijkplayer-java:0.8.8'
implementation 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.8'

# Other ABIs: optional
# 其他cpu架构,现在Android上架必要有64位的架构,所以arm64现在已成为必须
implementation 'tv.danmaku.ijk.media:ijkplayer-armv5:0.8.8'
implementation 'tv.danmaku.ijk.media:ijkplayer-arm64:0.8.8'
implementation 'tv.danmaku.ijk.media:ijkplayer-x86:0.8.8'
implementation 'tv.danmaku.ijk.media:ijkplayer-x86_64:0.8.8'

# ExoPlayer as IMediaPlayer: optional, experimental
# Exo播放器,引入这个才可获得IjkMediaPlayer对象
implementation 'tv.danmaku.ijk.media:ijkplayer-exo:0.8.8'

根据实际情况,我的项目只需要引入如下:

implementation 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.8'
implementation 'tv.danmaku.ijk.media:ijkplayer-arm64:0.8.8'
implementation 'tv.danmaku.ijk.media:ijkplayer-exo:0.8.8'

5.3、IjkMediaPlayer使用

5.3.1、初始化

IjkMediaPlayer player = new IjkMediaPlayer();

5.3.2、配置播放源

player.setDataSource(path);

path可以是本地的地址;也可以是在线音频地址,可用陈奕迅-孤勇者;也可直接播放rtmp流,找了很久没找到国内能播出来的电视台rtmp地址,最后用了这个:rtmp://media3.scctv.net/live/scctv_800

5.3.3、播放完成监听

player.setOnCompletionListener(OnCompletionListener listener)

不管播放成功与否,执行播放过程完成或视频播放完之后,就会回调完成方法

5.3.4、准备监听

player.setOnPreparedListener(OnPreparedListener listener)

在调用完成prepareAsync()之后,会回调该监听事件,但回调成功后,则可执行start方法播放

5.3.5、播放

//准备
player.prepareAsync();

player.setOnPreparedListener(iMediaPlayer -> {
    iMediaPlayer.start();

});

5.3.6、停止播放

停止播放是一套组合拳

  1. 停止播放
  2. 重置
  3. 释放
//停止播放
player.stop();
//重置状态
player.reset();
//释放相关资源
player.release();

6、补充

6.1、SdcardUtils

public class SdcardUtils {
    /**
     * 检查是否存在SD卡
     */
    public static boolean hasSdcard() {
        String state = Environment.getExternalStorageState();
        return state.equals(Environment.MEDIA_MOUNTED);
    }

    public static File getPublicFile(Context context, String child) {
        File file;
        if (hasSdcard()) {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
                file = new File(Environment.getExternalStorageDirectory(), child);
            } else {
                file = new File(context.getExternalFilesDir(Environment.DIRECTORY_MUSIC), child);
            }
        } else {
            file = new File(context.getFilesDir(), child);
        }

        mkdir(file.getParentFile());

        return file;
    }

    private static File mkdir(File dir) {
        if (!dir.exists()) {
            dir.mkdirs();
        }
        return dir;
    }
}

6.2、参考

音频采样率
安卓Android开发:使用AudioRecord录音、将录音保存为wav文件、使用AudioTrack保存录音
音视频基础概念:PCM、采样率、位深和比特率
Android pcm编码为aac
Pcm 转 AAc
Android 音频焦点管理文章来源地址https://www.toymoban.com/news/detail-407615.html

到了这里,关于Android录制音频并使用ijkplayer播放的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • RK3568 alsa 音频 录制与播放

    ALSA是Advanced Linux Sound Architecture 的缩写,目前已经成为了linux的主流音频体系结构。 在内核设备驱动层,ALSA提供了alsa-driver,同时在应用层,ALSA为咱们提供了alsa-lib,应用程序只要调用alsa-lib提供的API,便可以完成对底层音频硬件的控制。 aplay -l 显示实际声卡序号 查看声卡:

    2024年02月13日
    浏览(41)
  • HarmonyOS实战开发-录音机、如何实现音频录制和播放的功能

    本示例使用audio相关接口实现音频录制和播放的功能,使用mediaLibrary实现音频文件的管理。 相关概念: AudioRecorder:音频录制的主要工作是捕获音频信号,完成音频编码并保存到文件中,帮助开发者轻松实现音频录制功能。它允许调用者指定音频录制的采样率、声道数、编码

    2024年04月17日
    浏览(113)
  • Android 使用webView打开网页可以实现自动播放音频

    使用webview 自动播放音视频,场景如,流媒体自动部分,音视频通话等。会出现如下问题:   解决方案如下: 配置webview 如下,这样可以自动播放音频。

    2024年02月12日
    浏览(64)
  • Android13音频录制适配

    之前写过一篇音频录制的文章,当时是在Android10以下的手机可以成功录制和播放,但是Android10及以上手机提示创建文件失败,最近做过Android13的适配,索性一起把之前的录音也适配了,记录一下适配的过程。 主要就是文件的生成和创建,由于Android10以后不能随意创建私有文件

    2024年02月21日
    浏览(44)
  • Android 音频(一) _ 采样量化编码 & AudioRecord 录制音频

    模拟信号 音频承载着声音信息,而声音是连续变化的信息。物理中把承载信息的载体称为 信号 ,把连续变化的信息称为 模拟信号 ,它在坐标轴中表现为如下形态: 计算机只能处理0和1,即离散值。音频这种模拟信号得转换成离散值才能被计算机处理。这个转化过程称为 模

    2023年04月09日
    浏览(44)
  • android 调用系统录制视频和音频

    1、录制视频 请先申请相机权限 2、录制音频 请先申请权限

    2023年04月08日
    浏览(41)
  • Android webrtc实战(一)录制本地视频并播放,附带详细的基础知识讲解

    目录 一、创建PeerConnectionFactory 初始化 构建对象 二、创建AudioDeviceModule AudioDeviceModule JavaAudioDeviceModule 构建对象 setAudioAttributes setAudioFormat setAudioSource 创建录制视频相关对象 创建VideoSource 创建VideoCapturer 创建VideoTrack 播放视频 切换前后置摄像头 别忘了申请权限 完整代码 本系列

    2024年02月16日
    浏览(55)
  • uniapp - [微信小程序] 超详细实时录音功能,录制外部声音及播放上传到服务器(支持录音完毕生成 mp3、试听音频(带进度条)、暂停录音、重新录音、限制录音最大时长、自定义音频名称等功能)

    网上的教程都太乱了,功能不好用且一堆 BUG,没有注释很难改造示例为自己用。 本文实现了 uniapp 微信小程序平台,授权麦克风进行录音并保存为音频的功能,内置播放器可播放录音文件, 您直接复制组件源码,稍微改改样式就能直接应用到您的项目中去了。 如下图安卓、

    2024年02月12日
    浏览(326)
  • 使用NAudio录制wav音频

    NAudio官网 Unity2019.4.34f1c1 Window10 NAudio 1.10 .Net 3.5 StartRecording方法 启用录制 StopRecording方法 停止录制 DataAvailable 录制中回调 RecordingStopped 录制结束回调 Write方法可存储音频 WaveIn.DeviceCount 返回音频捕获设备数量 台式电脑,可能需要插入耳机才可以启用麦克风 检测到麦克风,显示

    2024年02月12日
    浏览(35)
  • 安卓使用MediaRecorder录制音频的详细使用

    本文首发地址 https://blog.csdn.net/CSqingchen/article/details/134599828 最新更新地址 https://gitee.com/chenjim/chenjimblog 在Android开发过程中,我们经常需要处理音频或视频相关的功能。比如,我们要做一个简单的录音机或者录像机。 在Android中录制音频有两种方式: MediaRecorder 和 AudioRecord 。两

    2024年01月21日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包